Skip to content

Commit b6c1947

Browse files
authored
[Rollout] Production rollout 2025-05-22 (#4874)
#4873
2 parents 5d2b9c1 + 3dad988 commit b6c1947

File tree

12 files changed

+308
-6
lines changed

12 files changed

+308
-6
lines changed

src/Maestro/Maestro.MergePolicies/DependencyUpdateSummary.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@ public class DependencyUpdateSummary
2626

2727
public DependencyUpdateSummary(DependencyUpdate du)
2828
{
29-
DependencyName = du.To.Name;
30-
FromVersion = du.From.Version;
31-
ToVersion = du.To.Version;
32-
FromCommitSha = du.From.Commit;
33-
ToCommitSha = du.To.Commit;
29+
DependencyName = du.To?.Name;
30+
FromVersion = du.From?.Version;
31+
ToVersion = du.To?.Version;
32+
FromCommitSha = du.From?.Commit;
33+
ToCommitSha = du.To?.Commit;
3434
}
3535

3636
public DependencyUpdateSummary() { }
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Net;
6+
using System.Threading.Tasks;
7+
using Microsoft.DotNet.Darc.Options;
8+
using Microsoft.DotNet.DarcLib;
9+
using Microsoft.DotNet.ProductConstructionService.Client;
10+
using Microsoft.Extensions.Logging;
11+
using Newtonsoft.Json;
12+
13+
namespace Microsoft.DotNet.Darc.Operations;
14+
15+
internal class UpdateChannelOperation : Operation
16+
{
17+
private readonly UpdateChannelCommandLineOptions _options;
18+
private readonly IBarApiClient _barClient;
19+
private readonly ILogger<UpdateChannelOperation> _logger;
20+
21+
public UpdateChannelOperation(
22+
UpdateChannelCommandLineOptions options,
23+
IBarApiClient barClient,
24+
ILogger<UpdateChannelOperation> logger)
25+
{
26+
_options = options;
27+
_barClient = barClient;
28+
_logger = logger;
29+
}
30+
31+
/// <summary>
32+
/// Updates an existing channel's metadata (name and/or classification).
33+
/// </summary>
34+
/// <returns>Process exit code.</returns>
35+
public override async Task<int> ExecuteAsync()
36+
{
37+
try
38+
{
39+
// Validate that at least one of name or classification is provided
40+
if (string.IsNullOrEmpty(_options.Name) && string.IsNullOrEmpty(_options.Classification))
41+
{
42+
_logger.LogError("Either --name or --classification (or both) must be specified.");
43+
return Constants.ErrorCode;
44+
}
45+
46+
// Retrieve the current channel information first to confirm it exists
47+
var channel = await _barClient.GetChannelAsync(_options.Id);
48+
if (channel == null)
49+
{
50+
_logger.LogError("Could not find a channel with id '{id}'", _options.Id);
51+
return Constants.ErrorCode;
52+
}
53+
54+
// Update the channel with the specified information
55+
var updatedChannel = await _barClient.UpdateChannelAsync(_options.Id, _options.Name, _options.Classification);
56+
57+
switch (_options.OutputFormat)
58+
{
59+
case DarcOutputType.json:
60+
Console.WriteLine(JsonConvert.SerializeObject(
61+
new
62+
{
63+
id = updatedChannel.Id,
64+
name = updatedChannel.Name,
65+
classification = updatedChannel.Classification
66+
},
67+
Formatting.Indented));
68+
break;
69+
case DarcOutputType.text:
70+
Console.WriteLine($"Successfully updated channel '{_options.Id}':");
71+
Console.WriteLine($" Name: {updatedChannel.Name}");
72+
Console.WriteLine($" Classification: {updatedChannel.Classification}");
73+
break;
74+
default:
75+
throw new NotImplementedException($"Output type {_options.OutputFormat} not supported by update-channel");
76+
}
77+
78+
return Constants.SuccessCode;
79+
}
80+
catch (AuthenticationException e)
81+
{
82+
Console.WriteLine(e.Message);
83+
return Constants.ErrorCode;
84+
}
85+
catch (RestApiException e) when (e.Response.Status == (int)HttpStatusCode.Conflict)
86+
{
87+
_logger.LogError($"A channel with the specified name already exists.");
88+
return Constants.ErrorCode;
89+
}
90+
catch (Exception e)
91+
{
92+
_logger.LogError(e, "Error: Failed to update channel.");
93+
return Constants.ErrorCode;
94+
}
95+
}
96+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using CommandLine;
5+
using Microsoft.DotNet.Darc.Operations;
6+
7+
namespace Microsoft.DotNet.Darc.Options;
8+
9+
[Verb("update-channel", HelpText = "Update an existing channel's metadata.")]
10+
internal class UpdateChannelCommandLineOptions : CommandLineOptions<UpdateChannelOperation>
11+
{
12+
[Option('i', "id", Required = true, HelpText = "Channel ID.")]
13+
public int Id { get; set; }
14+
15+
[Option('n', "name", HelpText = "New name of channel.")]
16+
public string Name { get; set; }
17+
18+
[Option('c', "classification", HelpText = "New classification of channel.")]
19+
public string Classification { get; set; }
20+
}

src/Microsoft.DotNet.Darc/Darc/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ public static Type[] GetOptions() =>
115115
typeof(SubscriptionsStatusCommandLineOptions),
116116
typeof(TriggerSubscriptionsCommandLineOptions),
117117
typeof(UpdateBuildCommandLineOptions),
118+
typeof(UpdateChannelCommandLineOptions),
118119
typeof(UpdateDependenciesCommandLineOptions),
119120
typeof(UpdateSubscriptionCommandLineOptions),
120121
typeof(VerifyCommandLineOptions),

src/Microsoft.DotNet.Darc/DarcLib/BarApiClient.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ public Task<Channel> DeleteChannelAsync(int id)
142142
return _barClient.Channels.DeleteChannelAsync(id);
143143
}
144144

145+
/// <summary>
146+
/// Update a channel with new metadata.
147+
/// </summary>
148+
/// <param name="id">Id of channel to update</param>
149+
/// <param name="name">Optional new name of channel</param>
150+
/// <param name="classification">Optional new classification of channel</param>
151+
/// <returns>Updated channel</returns>
152+
public Task<Channel> UpdateChannelAsync(int id, string? name = null, string? classification = null)
153+
{
154+
return _barClient.Channels.UpdateChannelAsync(id, classification, name);
155+
}
156+
145157
public async Task<DependencyFlowGraph> GetDependencyFlowGraphAsync(
146158
int channelId,
147159
int days,

src/Microsoft.DotNet.Darc/DarcLib/IBarApiClient.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@ Task<Subscription> CreateSubscriptionAsync(
162162
/// <returns>Channel just deleted</returns>
163163
Task<Channel> DeleteChannelAsync(int id);
164164

165+
/// <summary>
166+
/// Update a channel with new metadata.
167+
/// </summary>
168+
/// <param name="id">Id of channel to update</param>
169+
/// <param name="name">Optional new name of channel</param>
170+
/// <param name="classification">Optional new classification of channel</param>
171+
/// <returns>Updated channel</returns>
172+
Task<Channel> UpdateChannelAsync(int id, string? name = null, string? classification = null);
173+
165174
/// <summary>
166175
/// Retrieve the list of channels from the build asset registry.
167176
/// </summary>

src/Microsoft.DotNet.Darc/DarcLib/VirtualMonoRepo/VersionFileCodeFlowUpdater.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,22 @@ async Task AbortMerge()
242242
string.Join(", ", unresolvableConflicts));
243243

244244
await AbortMerge();
245+
246+
// If this is a first commit, we need to update dependencies still
247+
if (!headBranchExisted)
248+
{
249+
var updates = await BackflowDependenciesAndToolset(
250+
mapping.Name,
251+
repo,
252+
branchToMerge,
253+
build,
254+
excludedAssetsMatcher,
255+
lastFlow,
256+
(Backflow)currentFlow,
257+
cancellationToken);
258+
return new VersionFileUpdateResult(ConflictedFiles: [.. conflictedFiles.Select(file => new UnixPath(file))], updates);
259+
}
260+
245261
return new VersionFileUpdateResult([..conflictedFiles.Select(file => new UnixPath(file))], []);
246262
}
247263

src/ProductConstructionService/Microsoft.DotNet.ProductConstructionService.Client/Generated/Channels.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ Task<List<string>> ListRepositoriesAsync(
4242
CancellationToken cancellationToken = default
4343
);
4444

45+
Task<Models.Channel> UpdateChannelAsync(
46+
int id,
47+
string classification = default,
48+
string name = default,
49+
CancellationToken cancellationToken = default
50+
);
51+
4552
Task AddBuildToChannelAsync(
4653
int buildId,
4754
int channelId,
@@ -455,6 +462,86 @@ internal async Task OnDeleteChannelFailed(Request req, Response res)
455462
throw ex;
456463
}
457464

465+
partial void HandleFailedUpdateChannelRequest(RestApiException ex);
466+
467+
public async Task<Models.Channel> UpdateChannelAsync(
468+
int id,
469+
string classification = default,
470+
string name = default,
471+
CancellationToken cancellationToken = default
472+
)
473+
{
474+
475+
const string apiVersion = "2020-02-20";
476+
477+
var _baseUri = Client.Options.BaseUri;
478+
var _url = new RequestUriBuilder();
479+
_url.Reset(_baseUri);
480+
_url.AppendPath(
481+
"/api/channels/{id}".Replace("{id}", Uri.EscapeDataString(Client.Serialize(id))),
482+
false);
483+
484+
if (!string.IsNullOrEmpty(name))
485+
{
486+
_url.AppendQuery("name", Client.Serialize(name));
487+
}
488+
if (!string.IsNullOrEmpty(classification))
489+
{
490+
_url.AppendQuery("classification", Client.Serialize(classification));
491+
}
492+
_url.AppendQuery("api-version", Client.Serialize(apiVersion));
493+
494+
495+
using (var _req = Client.Pipeline.CreateRequest())
496+
{
497+
_req.Uri = _url;
498+
_req.Method = RequestMethod.Patch;
499+
500+
using (var _res = await Client.SendAsync(_req, cancellationToken).ConfigureAwait(false))
501+
{
502+
if (_res.Status < 200 || _res.Status >= 300)
503+
{
504+
await OnUpdateChannelFailed(_req, _res).ConfigureAwait(false);
505+
}
506+
507+
if (_res.ContentStream == null)
508+
{
509+
await OnUpdateChannelFailed(_req, _res).ConfigureAwait(false);
510+
}
511+
512+
using (var _reader = new StreamReader(_res.ContentStream))
513+
{
514+
var _content = await _reader.ReadToEndAsync().ConfigureAwait(false);
515+
var _body = Client.Deserialize<Models.Channel>(_content);
516+
return _body;
517+
}
518+
}
519+
}
520+
}
521+
522+
internal async Task OnUpdateChannelFailed(Request req, Response res)
523+
{
524+
string content = null;
525+
if (res.ContentStream != null)
526+
{
527+
using (var reader = new StreamReader(res.ContentStream))
528+
{
529+
content = await reader.ReadToEndAsync().ConfigureAwait(false);
530+
}
531+
}
532+
533+
var ex = new RestApiException<Models.ApiError>(
534+
req,
535+
res,
536+
content,
537+
Client.Deserialize<Models.ApiError>(content)
538+
);
539+
HandleFailedUpdateChannelRequest(ex);
540+
HandleFailedRequest(ex);
541+
Client.OnFailedRequest(ex);
542+
throw ex;
543+
}
544+
458545
partial void HandleFailedAddBuildToChannelRequest(RestApiException ex);
459546

460547
public async Task AddBuildToChannelAsync(

src/ProductConstructionService/ProductConstructionService.Api/Api/v2018_07_16/Controllers/ChannelsController.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,44 @@ public virtual async Task<IActionResult> RemoveBuildFromChannel(int channelId, i
238238
return StatusCode((int)HttpStatusCode.OK);
239239
}
240240

241+
/// <summary>
242+
/// Update metadata of a <see cref="Channel"/>.
243+
/// </summary>
244+
/// <param name="id">The id of the <see cref="Channel"/> to update</param>
245+
/// <param name="name">Optional new name of the <see cref="Channel"/></param>
246+
/// <param name="classification">Optional new classification of the <see cref="Channel"/></param>
247+
[HttpPatch("{id}")]
248+
[SwaggerApiResponse(HttpStatusCode.OK, Type = typeof(Channel), Description = "The Channel has been updated")]
249+
[SwaggerApiResponse(HttpStatusCode.BadRequest, Description = "At least one of name or classification must be specified")]
250+
[SwaggerApiResponse(HttpStatusCode.Conflict, Description = "A Channel with that name already exists")]
251+
[HandleDuplicateKeyRows("Could not update channel. A channel with the specified name already exists.")]
252+
public virtual async Task<IActionResult> UpdateChannel(int id, string? name = null, string? classification = null)
253+
{
254+
if (string.IsNullOrEmpty(name) && string.IsNullOrEmpty(classification))
255+
{
256+
return BadRequest(new ApiError("At least one of 'name' or 'classification' must be specified."));
257+
}
258+
259+
Maestro.Data.Models.Channel? channel = await _context.Channels.FindAsync(id);
260+
if (channel == null)
261+
{
262+
return NotFound();
263+
}
264+
265+
if (!string.IsNullOrEmpty(name))
266+
{
267+
channel.Name = name;
268+
}
269+
270+
if (!string.IsNullOrEmpty(classification))
271+
{
272+
channel.Classification = classification;
273+
}
274+
275+
await _context.SaveChangesAsync();
276+
return Ok(new Channel(channel));
277+
}
278+
241279
/// <summary>
242280
/// Add an existing <see cref="ReleasePipeline"/> to the specified <see cref="Channel"/>
243281
/// </summary>

src/ProductConstructionService/ProductConstructionService.Api/Api/v2020_02_20/Controllers/ChannelsController.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,22 @@ public override async Task<IActionResult> RemoveBuildFromChannel(int channelId,
237237
return StatusCode((int)HttpStatusCode.OK);
238238
}
239239

240+
/// <summary>
241+
/// Update metadata of a <see cref="Channel"/>.
242+
/// </summary>
243+
/// <param name="id">The id of the <see cref="Channel"/> to update</param>
244+
/// <param name="name">Optional new name of the <see cref="Channel"/></param>
245+
/// <param name="classification">Optional new classification of the <see cref="Channel"/></param>
246+
[HttpPatch("{id}")]
247+
[SwaggerApiResponse(HttpStatusCode.OK, Type = typeof(Channel), Description = "The Channel has been updated")]
248+
[SwaggerApiResponse(HttpStatusCode.BadRequest, Description = "At least one of name or classification must be specified")]
249+
[SwaggerApiResponse(HttpStatusCode.Conflict, Description = "A Channel with that name already exists")]
250+
[HandleDuplicateKeyRows("Could not update channel. A channel with the specified name already exists.")]
251+
public override async Task<IActionResult> UpdateChannel(int id, string? name = null, string? classification = null)
252+
{
253+
return await base.UpdateChannel(id, name, classification);
254+
}
255+
240256
[ApiRemoved]
241257
public override Task<IActionResult> AddPipelineToChannel(int channelId, int pipelineId)
242258
{

0 commit comments

Comments
 (0)