WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 798e08c

Browse files
authored
[Rollout] Production rollout 2025.02.10 (#4435)
<!-- Link the GitHub or AzDO issue this pull request is associated with. Please copy and paste the full URL rather than using the dotnet/arcade-services# syntax --> #4434
2 parents 80c026a + f88ace0 commit 798e08c

File tree

9 files changed

+386
-208
lines changed

9 files changed

+386
-208
lines changed

src/Microsoft.DotNet.Darc/DarcLib/Helpers/HttpRequestManager.cs

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,20 +49,26 @@ public HttpRequestManager(
4949

5050
public async Task<HttpResponseMessage> ExecuteAsync(int retryCount = 3)
5151
{
52-
var retriesRemaining = retryCount;
52+
var retriesRemaining = retryCount + 1;
53+
var attempts = 0;
5354
// Add a bit of randomness to the retry delay.
5455
var rng = new Random();
5556

56-
HttpStatusCode[] stopRetriesHttpStatusCodes = [
57+
HttpStatusCode[] stopRetriesHttpStatusCodes =
58+
[
5759
HttpStatusCode.NotFound,
5860
HttpStatusCode.UnprocessableEntity,
5961
HttpStatusCode.BadRequest,
6062
HttpStatusCode.Unauthorized,
61-
HttpStatusCode.Forbidden ];
63+
HttpStatusCode.Forbidden
64+
];
6265

6366
while (true)
6467
{
68+
retriesRemaining--;
6569
HttpResponseMessage response = null;
70+
var delay = TimeSpan.FromSeconds((retryCount - retriesRemaining) * rng.Next(1, 7));
71+
attempts++;
6672

6773
try
6874
{
@@ -94,7 +100,7 @@ public async Task<HttpResponseMessage> ExecuteAsync(int retryCount = 3)
94100
}
95101

96102
_logger.LogError(
97-
"A '{httpCode} - {status}' status was returned for a HTTP request. We'll set the retries amount to 0. {error}",
103+
"Non-continuable status '{httpCode} - {status}' was returned for a HTTP request. {error}",
98104
(int)response.StatusCode,
99105
response.StatusCode,
100106
errorDetails);
@@ -124,28 +130,30 @@ public async Task<HttpResponseMessage> ExecuteAsync(int retryCount = 3)
124130
{
125131
if (_logFailure)
126132
{
127-
_logger.LogError("There was an error executing method '{method}' against URI '{requestUri}' " +
128-
"after {maxRetries} attempts. Exception: {exception}",
129-
_method,
130-
_requestUri,
131-
retryCount,
132-
ex);
133+
_logger.LogError(
134+
"Executing HTTP {method} againt '{requestUri}' failed after {attempts} attempts. Exception: {exception}",
135+
_method,
136+
_requestUri,
137+
attempts,
138+
ex);
133139
}
134140
throw;
135141
}
136-
else if (_logFailure)
142+
143+
if (_logFailure)
137144
{
138-
_logger.LogWarning("There was an error executing method '{method}' against URI '{requestUri}'. " +
139-
"{retriesRemaining} attempts remaining. Exception: {ex.ToString()}",
140-
_method,
141-
_requestUri,
142-
retriesRemaining,
143-
ex);
145+
_logger.LogWarning(
146+
"HTTP {method} against '{requestUri}' failed. {retriesRemaining} attempts remaining. " +
147+
"Will retry in {retryDelay}. Exception: {exception}",
148+
_method,
149+
_requestUri,
150+
retriesRemaining,
151+
delay,
152+
ex);
144153
}
154+
155+
await Task.Delay(delay);
145156
}
146-
--retriesRemaining;
147-
var delay = (retryCount - retriesRemaining) * rng.Next(1, 7);
148-
await Task.Delay(delay * 1000);
149157
}
150158
}
151159
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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 Microsoft.DotNet.GitHub.Authentication;
5+
using Octokit;
6+
7+
namespace ProductConstructionService.Api.Api;
8+
9+
public interface IGitHubInstallationIdResolver
10+
{
11+
Task<long?> GetInstallationIdForRepository(string repoUri);
12+
}
13+
14+
public class GitHubInstallationIdResolver : IGitHubInstallationIdResolver
15+
{
16+
private readonly IGitHubTokenProvider _gitHubTokenProvider;
17+
private readonly ILogger<GitHubInstallationIdResolver> _logger;
18+
19+
public GitHubInstallationIdResolver(
20+
IGitHubTokenProvider gitHubTokenProvider,
21+
ILogger<GitHubInstallationIdResolver> logger)
22+
{
23+
_gitHubTokenProvider = gitHubTokenProvider;
24+
_logger = logger;
25+
}
26+
27+
public async Task<long?> GetInstallationIdForRepository(string repoUri)
28+
{
29+
_logger.LogInformation("Getting installation ID for {repoUri}", repoUri);
30+
31+
var (owner, repo) = Microsoft.DotNet.DarcLib.GitHubClient.ParseRepoUri(repoUri);
32+
var token = _gitHubTokenProvider.GetTokenForApp();
33+
var client = new GitHubClient(new ProductHeaderValue(nameof(ProductConstructionService)))
34+
{
35+
Credentials = new Credentials(token, AuthenticationType.Bearer)
36+
};
37+
38+
try
39+
{
40+
var installation = await client.GitHubApps.GetRepositoryInstallationForCurrent(owner, repo);
41+
42+
if (installation == null)
43+
{
44+
_logger.LogInformation("Failed to get installation id for {owner}/{repo}", owner, repo);
45+
return null;
46+
}
47+
48+
_logger.LogInformation("Installation id for {owner}/{repo} is {installationId}", owner, repo, installation.Id);
49+
return installation.Id;
50+
}
51+
catch (ApiException e)
52+
{
53+
_logger.LogInformation("Failed to get installation id for {owner}/{repo} - {statusCode}.", owner, repo, e.StatusCode);
54+
return null;
55+
}
56+
}
57+
}

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

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,18 @@ public class SubscriptionsController : ControllerBase
2626
{
2727
private readonly BuildAssetRegistryContext _context;
2828
private readonly IWorkItemProducerFactory _workItemProducerFactory;
29+
private readonly IGitHubInstallationIdResolver _installationIdResolver;
2930
private readonly ILogger<SubscriptionsController> _logger;
3031

3132
public SubscriptionsController(
3233
BuildAssetRegistryContext context,
3334
IWorkItemProducerFactory workItemProducerFactory,
35+
IGitHubInstallationIdResolver installationIdResolver,
3436
ILogger<SubscriptionsController> logger)
3537
{
3638
_context = context;
3739
_workItemProducerFactory = workItemProducerFactory;
40+
_installationIdResolver = installationIdResolver;
3841
_logger = logger;
3942
}
4043

@@ -452,6 +455,63 @@ public virtual async Task<IActionResult> Create([FromBody, Required] Subscriptio
452455
new Subscription(subscriptionModel));
453456
}
454457

458+
/// <summary>
459+
/// Verifies that the repository is registered in the database (and has a valid installation ID).
460+
/// </summary>
461+
protected async Task<bool> EnsureRepositoryRegistration(string repoUri)
462+
{
463+
Maestro.Data.Models.Repository? repo = await _context.Repositories.FindAsync(repoUri);
464+
465+
// If we have no repository information or an invalid installation ID, we need to register the repository
466+
if (repoUri.Contains("github.com"))
467+
{
468+
if (repo?.InstallationId > 0)
469+
{
470+
return true;
471+
}
472+
473+
var installationId = await _installationIdResolver.GetInstallationIdForRepository(repoUri);
474+
475+
if (!installationId.HasValue)
476+
{
477+
return false;
478+
}
479+
480+
if (repo == null)
481+
{
482+
_context.Repositories.Add(
483+
new Maestro.Data.Models.Repository
484+
{
485+
RepositoryName = repoUri,
486+
InstallationId = installationId.Value
487+
});
488+
}
489+
else
490+
{
491+
repo.InstallationId = installationId.Value;
492+
}
493+
return true;
494+
}
495+
496+
if (repoUri.Contains("dev.azure.com") && repo == null)
497+
{
498+
// In the case of a dev.azure.com repository, we don't have an app installation,
499+
// but we should add an entry in the repositories table, as this is required when
500+
// adding a new subscription policy.
501+
// NOTE:
502+
// There is a good chance here that we will need to also handle <account>.visualstudio.com
503+
// but leaving it out for now as it would be preferred to use the new format
504+
_context.Repositories.Add(
505+
new Maestro.Data.Models.Repository
506+
{
507+
RepositoryName = repoUri,
508+
InstallationId = default
509+
});
510+
}
511+
512+
return true;
513+
}
514+
455515
/// <summary>
456516
/// Find an existing subscription in the database with the same key data as the subscription we are adding/updating
457517
///

src/ProductConstructionService/ProductConstructionService.Api/Api/v2019_01_16/Controllers/SubscriptionsController.cs

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,16 @@ public class SubscriptionsController : v2018_07_16.Controllers.SubscriptionsCont
2525
public SubscriptionsController(
2626
BuildAssetRegistryContext context,
2727
IWorkItemProducerFactory workItemProducerFactory,
28+
IGitHubInstallationIdResolver gitHubInstallationRetriever,
2829
ILogger<SubscriptionsController> logger)
29-
: base(context, workItemProducerFactory, logger)
30+
: base(context, workItemProducerFactory, gitHubInstallationRetriever, logger)
3031
{
3132
_context = context;
3233
}
3334

3435
/// <summary>
3536
/// Gets a list of all <see cref="Subscription"/>s that match the given search criteria.
3637
/// </summary>
37-
/// <param name="sourceRepository"></param>
38-
/// <param name="targetRepository"></param>
39-
/// <param name="channelId"></param>
40-
/// <param name="enabled"></param>
4138
[HttpGet]
4239
[SwaggerApiResponse(HttpStatusCode.OK, Type = typeof(List<Subscription>), Description = "The list of Subscriptions")]
4340
[ValidateModelState]
@@ -233,41 +230,13 @@ public override async Task<IActionResult> Create([FromBody, Required] ProductCon
233230
new[] { $"The channel '{subscription.ChannelName}' could not be found." }));
234231
}
235232

236-
Maestro.Data.Models.Repository? repo = await _context.Repositories.FindAsync(subscription.TargetRepository);
237-
238-
if (subscription.TargetRepository.Contains("github.com"))
239-
{
240-
// If we have no repository information or an invalid installation id
241-
// then we will fail when trying to update things, so we fail early.
242-
if (repo == null || repo.InstallationId <= 0)
243-
{
244-
return BadRequest(
245-
new ApiError(
246-
"the request is invalid",
247-
new[]
248-
{
249-
$"The repository '{subscription.TargetRepository}' does not have an associated github installation. " +
250-
"The Maestro github application must be installed by the repository's owner and given access to the repository."
251-
}));
252-
}
253-
}
254-
// In the case of a dev.azure.com repository, we don't have an app installation,
255-
// but we should add an entry in the repositories table, as this is required when
256-
// adding a new subscription policy.
257-
// NOTE:
258-
// There is a good chance here that we will need to also handle <account>.visualstudio.com
259-
// but leaving it out for now as it would be preferred to use the new format
260-
else if (subscription.TargetRepository.Contains("dev.azure.com"))
233+
if (!await EnsureRepositoryRegistration(subscription.TargetRepository))
261234
{
262-
if (repo == null)
263-
{
264-
_context.Repositories.Add(
265-
new Maestro.Data.Models.Repository
266-
{
267-
RepositoryName = subscription.TargetRepository,
268-
InstallationId = default
269-
});
270-
}
235+
return BadRequest(new ApiError("The request is invalid",
236+
[
237+
$"No Maestro GitHub application installation found for repository '{subscription.TargetRepository}'. " +
238+
"The Maestro github application must be installed by the repository's owner and given access to the repository."
239+
]));
271240
}
272241

273242
Maestro.Data.Models.Subscription subscriptionModel = subscription.ToDb();

0 commit comments

Comments
 (0)