private async Task <HttpResponseMessage> SendRequestToApiAsync(HttpRequestMessage request) { using (var dependencyMeasurement = DependencyMeasurement.Start()) { HttpResponseMessage response = null; try { response = await _httpClient.SendAsync(request); _logger.LogRequest(request, response, dependencyMeasurement.Elapsed); return(response); } finally { try { var statusCode = response?.StatusCode ?? HttpStatusCode.InternalServerError; _logger.LogHttpDependency(request, statusCode, dependencyMeasurement); } catch (Exception ex) { _logger.LogWarning("Failed to log HTTP dependency. Reason: {Message}", ex.Message); } } } }
/// <summary> /// Retrieves the secret value, based on the given name /// </summary> /// <param name="secretName">The name of the secret</param> /// <returns>Returns a <see cref="Secret"/> that contains the secret</returns> /// <exception cref="ArgumentException">The <paramref name="secretName"/> must not be empty</exception> /// <exception cref="ArgumentNullException">The <paramref name="secretName"/> must not be null</exception> /// <exception cref="SecretNotFoundException">The secret was not found, using the given name</exception> /// <exception cref="KeyVaultErrorException">The call for a secret resulted in an invalid response</exception> public virtual async Task <Secret> GetSecretAsync(string secretName) { Guard.NotNullOrWhitespace(secretName, nameof(secretName), "Requires a non-blank secret name to request a secret in Azure Key Vault"); Guard.For <FormatException>(() => !SecretNameRegex.IsMatch(secretName), "Requires a secret name in the correct format to request a secret in Azure Key Vault, see https://docs.microsoft.com/en-us/azure/key-vault/general/about-keys-secrets-certificates#objects-identifiers-and-versioning"); var isSuccessful = false; using (DependencyMeasurement measurement = DependencyMeasurement.Start()) { try { Secret secret = await GetSecretCoreAsync(secretName); isSuccessful = true; return(secret); } finally { if (_options.TrackDependency) { Logger.LogDependency(DependencyName, secretName, VaultUri, isSuccessful, measurement); } } } }
private async Task <SecretData> GetTrackedSecretAsync(string secretName) { var context = new Dictionary <string, object> { ["SecretEngine Type"] = "KeyValue", ["SecretEngine Version"] = _options.KeyValueVersion }; var isSuccessful = false; using (DependencyMeasurement measurement = DependencyMeasurement.Start()) { try { _logger.LogTrace("Getting a secret {SecretName} from HashiCorp Vault {VaultUri}...", secretName, _vaultClient.Settings.VaultServerUriWithPort); SecretData result = await ReadSecretDataAsync(_secretPath); isSuccessful = true; return(result); } finally { _logger.LogTrace("{Result} secret from HashiCorp Vault {VaultUri}", isSuccessful ? "Got" : "Couldn't get", _vaultClient.Settings.VaultServerUriWithPort); if (_options.TrackDependency) { _logger.LogDependency(DependencyName, secretName, _vaultClient.Settings.VaultServerUriWithPort, isSuccessful, measurement, context); } } } }
/// <summary> /// Retrieves the secret value, based on the given name /// </summary> /// <param name="secretName">The name of the secret</param> /// <returns>Returns a <see cref="Secret"/> that contains the secret</returns> /// <exception cref="ArgumentException">The <paramref name="secretName"/> must not be empty</exception> /// <exception cref="ArgumentNullException">The <paramref name="secretName"/> must not be null</exception> /// <exception cref="SecretNotFoundException">The secret was not found, using the given name</exception> /// <exception cref="KeyVaultErrorException">The call for a secret resulted in an invalid response</exception> public virtual async Task <Secret> GetSecretAsync(string secretName) { Guard.NotNullOrWhitespace(secretName, nameof(secretName), "Requires a non-blank secret name to request a secret in Azure Key Vault"); var isSuccessful = false; #if NET6_0 using (DurationMeasurement measurement = DurationMeasurement.Start()) #else using (DependencyMeasurement measurement = DependencyMeasurement.Start()) #endif { try { Secret secret = await GetSecretCoreAsync(secretName); isSuccessful = true; return(secret); } finally { if (_options.TrackDependency) { Logger.LogDependency(DependencyName, secretName, VaultUri, isSuccessful, measurement); } } } }
public async Task DependencyMeasurement_StartMeasuringAction_HoldsStartAndElapsedTime() { // Act using (var measurement = DependencyMeasurement.Start()) { // Assert await Task.Delay(TimeSpan.FromMilliseconds(100)); Assert.NotNull(measurement); Assert.True(DateTimeOffset.UtcNow > measurement.StartTime, "Measurement should have lesser start time"); Assert.True(TimeSpan.Zero < measurement.Elapsed, "Measurement should be running"); } }
public async Task DependencyMeasurement_StopsMeasuringAction_WhenDisposed() { // Arrange var measurement = DependencyMeasurement.Start(); await Task.Delay(TimeSpan.FromMilliseconds(100)); measurement.Dispose(); var elapsed = measurement.Elapsed; // Act await Task.Delay(TimeSpan.FromMilliseconds(100)); // Assert Assert.NotNull(measurement); Assert.Equal(elapsed, measurement.Elapsed); }
/// <summary> /// Retrieves the secret value in the HashiCorp KeyValue Vault on given <paramref name="secretName"/> /// while tracking the dependency interaction call with the vault. /// </summary> /// <param name="secretName">The name of the HashiCorp secret.</param> /// <returns> /// The HashiCorp <see cref="SecretData"/> concrete instance that contains the secret value. /// </returns> /// <exception cref="ArgumentException">Thrown when the <paramref name="secretName"/> is blank.</exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when the <see cref="Options"/>'s <see cref="HashiCorpVaultOptions.KeyValueVersion"/> represents an unknown secret engine version. /// </exception> protected async Task <SecretData> GetTrackedSecretAsync(string secretName) { Guard.NotNullOrWhitespace(secretName, nameof(secretName), $"Requires a non-blank secret name to look up the secret in the HashiCorp Vault {Options.KeyValueVersion} KeyValue secret engine"); var context = new Dictionary <string, object> { ["SecretEngine Type"] = "KeyValue", ["SecretEngine Version"] = Options.KeyValueVersion }; var isSuccessful = false; #if NET6_0 using (DurationMeasurement measurement = DurationMeasurement.Start()) #else using (DependencyMeasurement measurement = DependencyMeasurement.Start()) #endif { try { Logger.LogTrace("Getting a secret {SecretName} from HashiCorp Vault {VaultUri}...", secretName, VaultClient.Settings.VaultServerUriWithPort); SecretData result = await ReadSecretDataAsync(); Logger.LogTrace("Secret '{SecretName}' was successfully retrieved from HashiCorp Vault {VaultUri}", secretName, VaultClient.Settings.VaultServerUriWithPort); isSuccessful = true; return(result); } catch (Exception exception) { Logger.LogError(exception, "Secret '{SecretName}' was not successfully retrieved from HashiCorp Vault {VaultUri}, cause: {Message}", secretName, VaultClient.Settings.VaultServerUriWithPort, exception.Message); throw; } finally { if (Options.TrackDependency) { Logger.LogDependency(DependencyName, secretName, VaultClient.Settings.VaultServerUriWithPort, isSuccessful, measurement, context); } } } }
private async Task <HttpResponseMessage> SendRequestToApiAsync(HttpRequestMessage request) { using (var dependencyMeasurement = DependencyMeasurement.Start()) { HttpResponseMessage response = null; try { _httpClient.BaseAddress = new Uri($"http://{_configuration.CurrentValue.Host}:{_configuration.CurrentValue.Port}"); response = await _httpClient.SendAsync(request); _logger.LogRequest(request, response, dependencyMeasurement.Elapsed); return(response); } finally { var statusCode = response?.StatusCode ?? HttpStatusCode.InternalServerError; _logger.LogHttpDependency(request, statusCode, dependencyMeasurement); } } }
private async Task <HttpResponseMessage> SendRequestToApiAsync(HttpRequestMessage request) { var client = CreateHttpClient(); using (var dependencyMeasurement = DependencyMeasurement.Start()) { HttpResponseMessage response = null; try { response = await client.SendAsync(request); _logger.LogRequest(request, response, dependencyMeasurement.Elapsed); return(response); } finally { var statusCode = response?.StatusCode ?? HttpStatusCode.InternalServerError; _logger.LogHttpDependency(request, statusCode, dependencyMeasurement); } } }
public async Task <List <Resource> > QueryAsync(string query, List <string> targetSubscriptions) { Guard.NotNullOrWhitespace(query, nameof(query)); var graphClient = await GetOrCreateClient(); bool isSuccessfulDependency = false; using (var dependencyMeasurement = DependencyMeasurement.Start()) { try { var queryRequest = new QueryRequest(targetSubscriptions, query); var response = await graphClient.ResourcesAsync(queryRequest); isSuccessfulDependency = true; var foundResources = ParseQueryResults(response); return(foundResources); } catch (ErrorResponseException responseException) { if (responseException.Response != null) { if (responseException.Response.StatusCode == HttpStatusCode.Forbidden) { var unauthorizedException = new UnauthorizedException(QueryApplicationId, targetSubscriptions); _logger.LogCritical(unauthorizedException, "Unable to query Azure Resource Graph"); throw unauthorizedException; } if (responseException.Response.StatusCode == HttpStatusCode.BadRequest) { var response = JToken.Parse(responseException.Response.Content); var errorDetails = response["error"]?["details"]; if (errorDetails != null) { var errorCodes = new List <string>(); foreach (var detailEntry in errorDetails) { errorCodes.Add(detailEntry["code"]?.ToString()); } if (errorCodes.Any(errorCode => errorCode.Equals("NoValidSubscriptionsInQueryRequest", StringComparison.InvariantCultureIgnoreCase))) { var invalidSubscriptionException = new QueryContainsInvalidSubscriptionException(targetSubscriptions); _logger.LogCritical(invalidSubscriptionException, "Unable to query Azure Resource Graph"); throw invalidSubscriptionException; } } } } throw; } finally { var contextualInformation = new Dictionary <string, object> { { "Query", query }, { "Subscriptions", targetSubscriptions } }; _logger.LogDependency("Azure Resource Graph", query, "Query", isSuccessfulDependency, dependencyMeasurement, contextualInformation); } } }
private async Task <TResponse> InteractWithAzureResourceGraphAsync <TResponse>(string queryName, string query, List <string> targetSubscriptions, Func <ResourceGraphClient, Task <TResponse> > interactionFunc) { Guard.NotNullOrWhitespace(query, nameof(query)); var retryPolicy = Policy.Handle <ErrorResponseException>(ex => ex.Response?.StatusCode == HttpStatusCode.Unauthorized) .RetryAsync(retryCount: 3, OnRetryAsync); return(await retryPolicy.ExecuteAsync(async() => { var graphClient = await GetOrCreateClient(); bool isSuccessfulDependency = false; using (var dependencyMeasurement = DependencyMeasurement.Start()) { try { var response = await interactionFunc(graphClient); isSuccessfulDependency = true; return response; } catch (ErrorResponseException responseException) { if (responseException.Response != null) { if (responseException.Response.StatusCode == HttpStatusCode.Forbidden) { var unauthorizedException = CreateUnauthorizedException(targetSubscriptions); throw unauthorizedException; } if (responseException.Response.StatusCode == HttpStatusCode.BadRequest) { var response = JToken.Parse(responseException.Response.Content); var errorDetails = response["error"]?["details"]; if (errorDetails != null) { var errorCodes = new List <string>(); foreach (var detailEntry in errorDetails) { errorCodes.Add(detailEntry["code"]?.ToString()); } if (errorCodes.Any(errorCode => errorCode.Equals("NoValidSubscriptionsInQueryRequest", StringComparison.InvariantCultureIgnoreCase))) { var invalidSubscriptionException = new QueryContainsInvalidSubscriptionException(targetSubscriptions); _logger.LogCritical(invalidSubscriptionException, "Unable to query Azure Resource Graph"); throw invalidSubscriptionException; } } } } throw; } finally { var contextualInformation = new Dictionary <string, object> { { "Query", query }, { "QueryName", queryName }, { "Subscriptions", targetSubscriptions } }; _logger.LogDependency("Azure Resource Graph", query, "Query", isSuccessfulDependency, dependencyMeasurement, contextualInformation); } } })); }