public static async Task Run( [TimerTrigger("0 0 0/1 * * *")] TimerInfo timerInfo, [CosmosDB( databaseName: Webhook.DatabaseName, collectionName: Webhook.CollectionName, ConnectionStringSetting = "CosmosDBConnectionString")] DocumentClient documentClient, ILogger log, ExecutionContext context) { var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); var dimensionConfigs = JsonConvert.DeserializeObject <DimensionConfig[]>(config["DIMENSION_CONFIG"]); log.LogTrace($"Dimension configs: {JsonConvert.SerializeObject(dimensionConfigs)}"); using (var httpClient = HttpClientFactory.Create()) { var token = await CronJob.GetToken(httpClient, config, log).ConfigureAwait(continueOnCapturedContext: false); httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}"); string sqlExpression = "Select * from c where c.processStatus=false"; using (var queryable = documentClient.CreateDocumentQuery <BillingEntry>(UriFactory.CreateDocumentCollectionUri(Webhook.DatabaseName, Webhook.CollectionName), sqlExpression).AsDocumentQuery()) { while (queryable.HasMoreResults) { foreach (var billingEntry in await queryable.ExecuteNextAsync <BillingEntry>().ConfigureAwait(continueOnCapturedContext: false)) { foreach (var dimensionConfig in dimensionConfigs) { var response = await CronJob.EmitUsageEvents(config, httpClient, dimensionConfig, billingEntry).ConfigureAwait(continueOnCapturedContext: false); var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); if (response.IsSuccessStatusCode) { log.LogTrace($"Successfully emitted a usage event. Reponse body: {responseBody}"); // update cosmosdb document billingEntry.processStatus = true; await documentClient.UpsertDocumentAsync(UriFactory.CreateDocumentCollectionUri(Webhook.DatabaseName, Webhook.CollectionName), billingEntry).ConfigureAwait(continueOnCapturedContext: false); } else { log.LogError($"Failed to emit a usage event. Error code: {response.StatusCode}. Failure cause: {response.ReasonPhrase}. Response body: {responseBody}"); } } } } } } }
public static async Task Run( [TimerTrigger("0 0 0/1 * * *")] TimerInfo timerInfo, ILogger log, ExecutionContext context) { var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); var dimensionConfigs = JsonConvert.DeserializeObject <DimensionConfig[]>(config["DIMENSION_CONFIG"]); log.LogTrace($"Dimension configs: {JsonConvert.SerializeObject(dimensionConfigs)}"); using (var armHttpClient = HttpClientFactory.Create()) { var armToken = await CronJob.GetToken(config, armHttpClient, log, "https://management.core.windows.net/").ConfigureAwait(continueOnCapturedContext: false); armHttpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {armToken}"); var applicationResourceId = await CronJob.GetResourceGroupManagedBy(config, armHttpClient, log).ConfigureAwait(continueOnCapturedContext: false); var application = await CronJob.GetApplication(applicationResourceId, config, armHttpClient, log).ConfigureAwait(continueOnCapturedContext: false); if (application != null) { log.LogInformation($"Authorization bearer token: {armToken}"); log.LogInformation($"Resource usage id: {application.Properties.BillingDetails?.ResourceUsageId}"); log.LogInformation($"Plan name: {application.Plan.Name}"); foreach (var dimensionConfig in dimensionConfigs) { var response = await CronJob.EmitUsageEvents(config, armHttpClient, dimensionConfig, application.Properties.BillingDetails?.ResourceUsageId, application.Plan.Name).ConfigureAwait(continueOnCapturedContext: false); var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); if (response.IsSuccessStatusCode) { log.LogTrace($"Successfully emitted a usage event. Reponse body: {responseBody}"); } else { log.LogError($"Failed to emit a usage event. Error code: {response.StatusCode}. Failure cause: {response.ReasonPhrase}. Response body: {responseBody}"); } } } } }
/// <summary> /// Emits the usage event to the configured MARKETPLACEAPI_URI. /// </summary> private static async Task <HttpResponseMessage> EmitUsageEvents(IConfigurationRoot config, HttpClient httpClient, DimensionConfig dimensionConfig, string resourceUsageId, string planId) { var usageEvent = new UsageEventDefinition { ResourceId = resourceUsageId, Quantity = dimensionConfig.Quantity, Dimension = dimensionConfig.Dimension, EffectiveStartTime = DateTime.UtcNow, PlanId = planId }; if (CronJob.IsLocalRun(config)) { return(new HttpResponseMessage { Content = new StringContent(JsonConvert.SerializeObject(usageEvent), UnicodeEncoding.UTF8, "application/json"), StatusCode = HttpStatusCode.OK }); } return(await httpClient.PostAsJsonAsync(config["MARKETPLACEAPI_URI"], usageEvent).ConfigureAwait(continueOnCapturedContext: false)); }
/// <summary> /// Gets the token for the attached user-assigned managed identity. /// </summary> public static async Task <string> GetToken(HttpClient httpClient, IConfigurationRoot config, ILogger log) { if (CronJob.IsLocalRun(config)) { return("token"); } // TOKEN_RESOURCE and MSI_CLIENT_ID come from the configs using (var request = new HttpRequestMessage(HttpMethod.Get, $"{config["MSI_ENDPOINT"]}/?resource={config["TOKEN_RESOURCE"]}&clientId={config["MSI_CLIENT_ID"]}&api-version=2017-09-01")) { request.Headers.Add("Secret", config["MSI_SECRET"]); var response = await httpClient.SendAsync(request).ConfigureAwait(continueOnCapturedContext: false); if (response?.IsSuccessStatusCode != true) { log.LogError("Failed to get token for user-assigned MSI. Please check that all the config flags are set properly and the MSI is attached."); } var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); return(JsonConvert.DeserializeObject <TokenDefinition>(responseBody).Access_token); } }
/// <summary> /// Gets the token for the system-assigned managed identity. /// </summary> private static async Task <string> GetToken(IConfigurationRoot config, HttpClient httpClient, ILogger log, string resource) { if (CronJob.IsLocalRun(config)) { return("token"); } // TOKEN_RESOURCE come from the configs using (var request = new HttpRequestMessage(HttpMethod.Get, $"{config["MSI_ENDPOINT"]}/?resource={resource}&api-version=2017-09-01")) { request.Headers.Add("Secret", config["MSI_SECRET"]); var response = await httpClient.SendAsync(request).ConfigureAwait(continueOnCapturedContext: false); if (response?.IsSuccessStatusCode != true) { log.LogError($"Failed to get token for system-assigned MSI. Please check that the MSI is set up properly. Error: {response.Content.ReadAsStringAsync().Result}"); } var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); return(JsonConvert.DeserializeObject <TokenDefinition>(responseBody).Access_token); } }