Skip to content

Commit bb9d080

Browse files
committed
fix: Add retry logic for federated identity credential creation
Resolves Request_ResourceNotFound errors during blueprint setup by implementing exponential backoff retry (5 attempts: 2s, 4s, 8s, 16s, 32s) to handle Azure AD application object propagation delays. Previously, the code attempted FIC creation immediately after a 10s delay, which was insufficient for Azure AD eventual consistency. Now automatically retries with clear user feedback when propagation errors are detected.
1 parent 874ce3e commit bb9d080

File tree

1 file changed

+35
-10
lines changed

1 file changed

+35
-10
lines changed

src/Microsoft.Agents.A365.DevTools.Cli/Services/A365SetupRunner.cs

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,7 @@ public async Task<bool> RunAsync(string configPath, string generatedConfigPath,
846846
/// <summary>
847847
/// Create Federated Identity Credential to link managed identity to blueprint
848848
/// Equivalent to createFederatedIdentityCredential function in PowerShell
849+
/// Implements retry logic to handle Azure AD propagation delays
849850
/// </summary>
850851
private async Task<bool> CreateFederatedIdentityCredentialAsync(
851852
string tenantId,
@@ -854,6 +855,9 @@ private async Task<bool> CreateFederatedIdentityCredentialAsync(
854855
string msiPrincipalId,
855856
CancellationToken ct)
856857
{
858+
const int maxRetries = 5;
859+
const int initialDelayMs = 2000; // Start with 2 seconds
860+
857861
try
858862
{
859863
var graphToken = await _graphService.GetGraphAccessTokenAsync(tenantId, ct);
@@ -875,23 +879,44 @@ private async Task<bool> CreateFederatedIdentityCredentialAsync(
875879
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", graphToken);
876880

877881
var url = $"https://graph.microsoft.com/v1.0/applications/{blueprintObjectId}/federatedIdentityCredentials";
878-
var response = await httpClient.PostAsync(
879-
url,
880-
new StringContent(federatedCredential.ToJsonString(), System.Text.Encoding.UTF8, "application/json"),
881-
ct);
882882

883-
if (!response.IsSuccessStatusCode)
883+
// Retry loop to handle propagation delays
884+
for (int attempt = 1; attempt <= maxRetries; attempt++)
884885
{
886+
var response = await httpClient.PostAsync(
887+
url,
888+
new StringContent(federatedCredential.ToJsonString(), System.Text.Encoding.UTF8, "application/json"),
889+
ct);
890+
891+
if (response.IsSuccessStatusCode)
892+
{
893+
_logger.LogInformation(" - Credential Name: {Name}", credentialName);
894+
_logger.LogInformation(" - Issuer: https://login.microsoftonline.com/{TenantId}/v2.0", tenantId);
895+
_logger.LogInformation(" - Subject (MSI Principal ID): {MsiId}", msiPrincipalId);
896+
return true;
897+
}
898+
885899
var error = await response.Content.ReadAsStringAsync(ct);
900+
901+
// Check if it's a propagation issue (resource not found)
902+
if (error.Contains("Request_ResourceNotFound") || error.Contains("does not exist"))
903+
{
904+
if (attempt < maxRetries)
905+
{
906+
var delayMs = initialDelayMs * (int)Math.Pow(2, attempt - 1); // Exponential backoff
907+
_logger.LogWarning("Application object not yet propagated (attempt {Attempt}/{MaxRetries}). Retrying in {Delay}ms...",
908+
attempt, maxRetries, delayMs);
909+
await Task.Delay(delayMs, ct);
910+
continue;
911+
}
912+
}
913+
914+
// Other error or max retries reached
886915
_logger.LogError("Failed to create federated identity credential: {Error}", error);
887916
return false;
888917
}
889918

890-
_logger.LogInformation(" - Credential Name: {Name}", credentialName);
891-
_logger.LogInformation(" - Issuer: https://login.microsoftonline.com/{TenantId}/v2.0", tenantId);
892-
_logger.LogInformation(" - Subject (MSI Principal ID): {MsiId}", msiPrincipalId);
893-
894-
return true;
919+
return false;
895920
}
896921
catch (Exception ex)
897922
{

0 commit comments

Comments
 (0)