public static async Task Run(TimerInfo myTimer, CloudTable configTbl, CloudTable statsTbl, CloudTable invalidTypesTbl, ICollector <string> outQueue, TraceWriter log) { _log = log; _outQueue = outQueue; log.Info("Starding subscription audit."); var invalidTagResourcesQuery = await invalidTypesTbl.ExecuteQuerySegmentedAsync(new TableQuery <InvalidTagResource>(), null); var auditConfigQuery = await configTbl.ExecuteQuerySegmentedAsync(new TableQuery <AuditConfig>(), null); // Init config table if new deployment if (auditConfigQuery.Results.Count == 0) { AuditConfig initConfig = new AuditConfig { SubscriptionId = "enter_valid_subscription_id", RowKey = Guid.NewGuid().ToString(), RequiredTags = "comma,separated,tag,list,here", PartitionKey = "init" }; TableOperation insertOperation = TableOperation.InsertOrReplace(initConfig); await configTbl.ExecuteAsync(insertOperation); log.Info("First run for new deployment. Please populate the AuditConfig table."); return; } foreach (var auditConfig in auditConfigQuery.Results) { try { AuditStats stats = new AuditStats { JobStart = DateTime.Now, PartitionKey = auditConfig.SubscriptionId, RowKey = Guid.NewGuid().ToString() }; IEnumerable <string> requiredTagsList = auditConfig.RequiredTags.Split(','); try { string token = await AuthenticationService.GetAccessTokenAsync(); _resourceManager = new ResourceManagerService(token); } catch (Exception ex) { log.Error("Unable to connect to the ARM API, Message: " + ex.Message); } await ProcessResourceGroups(requiredTagsList, invalidTagResourcesQuery.Results, auditConfig.SubscriptionId, stats); log.Info("Completed audit of subscription: " + auditConfig.SubscriptionId); stats.JobEnd = DateTime.Now; TableOperation insertOperation = TableOperation.InsertOrReplace(stats); await statsTbl.ExecuteAsync(insertOperation); } catch (Exception ex) { log.Error("Failure processing resource groups for auditConfig: " + auditConfig.RowKey); log.Error(ex.Message); } } }
static async Task <List <ResourceItem> > ProcessResourceGroups(IEnumerable <string> requiredTagsList, string subscriptionId, AuditStats stats) { List <ResourceItem> updateList = new List <ResourceItem>(); List <string> invalidResourceTypes = _resourceTypes.Where(t => !String.IsNullOrEmpty(t.ErrorMessage)).Select(t => t.Type).ToList(); var resourceGroups = await _resourceManager.GetResourceGroups(subscriptionId); stats.ResourceGroupsTotal = resourceGroups.Count; foreach (var rg in resourceGroups) { _log.Info("Resource group: " + rg.Name); if (rg.Tags == null) { _log.Warning("Resource group: " + rg.Name + " does not have tags."); continue; } var tagsToSync = TagService.GetRequiredTags((Dictionary <string, string>)rg.Tags, requiredTagsList); if (tagsToSync.Count < 1) { _log.Warning("Resource group: " + rg.Name + " does not have required tags."); stats.ResourceGroupsSkipped += 1; } else { List <ResourceItem> resources = await _resourceManager.GetResources(rg.Name, subscriptionId, invalidResourceTypes); stats.ResourceItemsTotal = resources.Count(); foreach (var resource in resources) { var result = TagService.GetTagUpdates(resource.Tags.ToDictionary(x => x.Key, x => x.Value), tagsToSync); if (result.Count > 0) { try { stats.ResourceItemsWithUpdates += 1; resource.Tags = result; resource.ApiVersion = await GetApiVersion(resource); updateList.Add(resource); } catch (Exception ex) { _log.Error("Failure processing resource: " + resource.Id); _log.Error(ex.Message); } } else { stats.ResourceItemsSkipped += 1; } } } } return(updateList); }
public static async Task Run( [TimerTrigger("0 0 */4 * * *", RunOnStartup = false)] TimerInfo timer, [Table("AuditConfig")] CloudTable configTbl, [Table("AuditStats")] CloudTable statsTbl, [Table("ResourceTypes")] CloudTable resourceTypesTbl, [Queue("resources-to-tag")] ICollector <string> outQueue, TraceWriter log) { _log = log; _resourceTypesTbl = resourceTypesTbl; log.Info("Starding subscription audit."); var resourceTypesQuery = await resourceTypesTbl.ExecuteQuerySegmentedAsync(new TableQuery <ResourceType>(), null); _resourceTypes = resourceTypesQuery.Results; var auditConfigQuery = await configTbl.ExecuteQuerySegmentedAsync(new TableQuery <AuditConfig>(), null); // Init config table if new deployment if (auditConfigQuery.Results.Count == 0) { AuditConfig initConfig = new AuditConfig { SubscriptionId = "enter_valid_subscription_id", RowKey = Guid.NewGuid().ToString(), RequiredTags = "comma,separated,tag,list,here", PartitionKey = "init" }; TableOperation insertOperation = TableOperation.InsertOrReplace(initConfig); await configTbl.ExecuteAsync(insertOperation); log.Info("First run for new deployment. Please populate the AuditConfig table."); return; } foreach (var auditConfig in auditConfigQuery.Results) { try { AuditStats stats = new AuditStats { JobStart = DateTime.Now, PartitionKey = auditConfig.SubscriptionId, RowKey = Guid.NewGuid().ToString() }; IEnumerable <string> requiredTagsList = auditConfig.RequiredTags.Split(','); try { string token = AuthenticationService.GetAccessTokenAsync(); _resourceManager = new ResourceManagerService(token); } catch (Exception ex) { log.Error("Unable to connect to the ARM API, Message: " + ex.Message); } List <ResourceItem> tagUpdates = await ProcessResourceGroups(requiredTagsList, auditConfig.SubscriptionId, stats); foreach (ResourceItem resourceItem in tagUpdates) { string messageText = JsonConvert.SerializeObject(resourceItem); _log.Info("Requesting tags for: " + resourceItem.Id); outQueue.Add(messageText); } log.Info("Completed audit of subscription: " + auditConfig.SubscriptionId); stats.JobEnd = DateTime.Now; TableOperation insertOperation = TableOperation.InsertOrReplace(stats); await statsTbl.ExecuteAsync(insertOperation); } catch (Exception ex) { log.Error("Failure processing resource groups for auditConfig: " + auditConfig.RowKey); log.Error(ex.Message); } } }
public static async Task Run( [TimerTrigger("0 0 */2 * * *")] TimerInfo myTimer, [Table("AuditConfig")] CloudTable configTbl, [Table("AuditStats")] CloudTable statsTbl, [Table("InvalidTagResources")] CloudTable invalidTypesTbl, [Queue("resources-to-tag")] ICollector <string> outQueue, TraceWriter log) { _log = log; _outQueue = outQueue; log.Info("Starding subscription audit."); var invalidTagResourcesQuery = await invalidTypesTbl.ExecuteQuerySegmentedAsync(new TableQuery <InvalidTagResource>(), null); var auditConfigQuery = await configTbl.ExecuteQuerySegmentedAsync(new TableQuery <AuditConfig>(), null); TokenCredentials tokenCredential; if (Environment.GetEnvironmentVariable("MSI_ENDPOINT") == null) { log.Info("Using service principal"); string appId = Environment.GetEnvironmentVariable("appId"); string appSecret = Environment.GetEnvironmentVariable("appSecret"); string tenantId = Environment.GetEnvironmentVariable("tenantId"); tokenCredential = AuthenticationService.GetAccessToken(appId, appSecret, tenantId); } else { log.Info("Using MSI"); var azureServiceTokenProvider = new AzureServiceTokenProvider(); string token = await azureServiceTokenProvider.GetAccessTokenAsync("https://management.core.windows.net/"); tokenCredential = new TokenCredentials(token); } try { _client = new ResourceManagementClient(tokenCredential); } catch (Exception ex) { log.Error("Unable to connect to the ARM API, Message: " + ex.Message); } // Init config table if new deployment if (auditConfigQuery.Results.Count == 0) { AuditConfig initConfig = new AuditConfig { SubscriptionId = "enter_valid_subscription_id", RowKey = Guid.NewGuid().ToString(), RequiredTags = "comma,separated,tag,list,here", PartitionKey = "init" }; TableOperation insertOperation = TableOperation.InsertOrReplace(initConfig); await configTbl.ExecuteAsync(insertOperation); log.Info("First run for new deployment. Please populate the AuditConfig table."); return; } foreach (var auditConfig in auditConfigQuery.Results) { try { AuditStats stats = new AuditStats { JobStart = DateTime.Now, PartitionKey = auditConfig.SubscriptionId, RowKey = Guid.NewGuid().ToString() }; IEnumerable <string> requiredTagsList = auditConfig.RequiredTags.Split(','); _client.SubscriptionId = auditConfig.SubscriptionId; IEnumerable <ResourceGroupInner> resourceGroups = await _client.ResourceGroups.ListAsync(); stats.ResourceGroupsTotal = resourceGroups.Count(); await ProcessResourceGroups(requiredTagsList, resourceGroups, invalidTagResourcesQuery.Results, auditConfig.SubscriptionId, stats); log.Info("Completed audit of subscription: " + auditConfig.SubscriptionId); stats.JobEnd = DateTime.Now; TableOperation insertOperation = TableOperation.InsertOrReplace(stats); await statsTbl.ExecuteAsync(insertOperation); } catch (Exception ex) { log.Error("Failure processing resource groups for auditConfig: " + auditConfig.RowKey); log.Error(ex.Message); } } }
static async Task ProcessResourceGroups(IEnumerable <string> requiredTagsList, IEnumerable <ResourceGroupInner> resourceGroups, List <InvalidTagResource> invalidTypes, string subscriptionId, AuditStats stats) { foreach (ResourceGroupInner rg in resourceGroups) { _log.Info("*** Resource Group: " + rg.Name); Dictionary <string, string> requiredRgTags = new Dictionary <string, string>(); foreach (string tagKey in requiredTagsList) { if (rg.Tags != null && rg.Tags.ContainsKey(tagKey)) { requiredRgTags.Add(tagKey, rg.Tags[tagKey]); } } if (requiredRgTags.Count != requiredTagsList.Count()) { _log.Warning("Resource group: " + rg.Name + " does not have required tags."); stats.ResourceGroupsSkipped += 1; } else { IEnumerable <GenericResourceInner> resources = await _client.Resources.ListByResourceGroupAsync(rg.Name); stats.ResourceItemsTotal = resources.Count(); foreach (var resource in resources) { // InvalidTagResource invalidResourceMatch = invalidTagResourcesQuery.Results.Where(x => x.Type == resource.Type).FirstOrDefault(); InvalidTagResource invalidType = invalidTypes.Where(x => x.Type == resource.Type).FirstOrDefault(); if (invalidType == null) { string apiVersion; try { apiVersion = await GetApiVersion(_client, resource.Type); } catch (Exception ex) { _log.Error(ex.Message); break; } var result = SetTags(resource.Tags, requiredRgTags); if (result.Count > 0) { stats.ResourceItemsWithUpdates += 1; resource.Tags = result; ResourceItem newItem = new ResourceItem { Id = resource.Id, ApiVersion = apiVersion, Location = resource.Location, Tags = resource.Tags, Type = resource.Type, Subscription = subscriptionId }; string messageText = JsonConvert.SerializeObject(newItem); _outQueue.Add(messageText); _log.Info("Requesting tags for: " + resource.Id); } else { stats.ResourceItemsSkipped += 1; } } else { _log.Warning("Item type does not support tagging: " + resource.Type); stats.ResourceItemsSkipped += 1; } } } } }
static async Task ProcessResourceGroups(IEnumerable <string> requiredTagsList, List <InvalidTagResource> invalidTypes, string subscriptionId, AuditStats stats) { var resourceGroups = await _resourceManager.GetResourceGroups(subscriptionId); stats.ResourceGroupsTotal = resourceGroups.Count; foreach (var rg in resourceGroups) { _log.Info("*** Resource Group: " + rg.Name); var tagsToSync = TagService.GetRequiredTags((Dictionary <string, string>)rg.Tags, requiredTagsList); if (tagsToSync.Count < 1) { _log.Warning("Resource group: " + rg.Name + " does not have required tags."); stats.ResourceGroupsSkipped += 1; } else { List <ResourceItem> resources = await _resourceManager.GetResources(rg.Name, subscriptionId, invalidTypes.Select(t => t.Type).ToList()); stats.ResourceItemsTotal = resources.Count(); foreach (var resource in resources) { var result = TagService.GetTagUpdates(resource.Tags.ToDictionary(x => x.Key, x => x.Value), tagsToSync); if (result.Count > 0) { stats.ResourceItemsWithUpdates += 1; resource.Tags = result; string messageText = JsonConvert.SerializeObject(resource); _log.Info("Requesting tags for: " + resource.Id); _outQueue.Add(messageText); } else { stats.ResourceItemsSkipped += 1; } } } } }