internal/renderers/report_data.go (310 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. package renderers import ( "fmt" "strings" "github.com/Azure/azqr/internal/models" ) type ( ReportData struct { OutputFileName string Mask bool Azqr []models.AzqrServiceResult Aprl []models.AprlResult Defender []models.DefenderResult DefenderRecommendations []models.DefenderRecommendation Advisor []models.AdvisorResult Cost *models.CostResult Recommendations map[string]map[string]models.AprlRecommendation Resources []*models.Resource ExludedResources []*models.Resource ResourceTypeCount []models.ResourceTypeCount } ResourceTypeCountResults struct { ResourceType []models.ResourceTypeCount `json:"ResourceType"` } ) func (rd *ReportData) ResourcesTable() [][]string { return rd.resourcesTable(rd.Resources) } func (rd *ReportData) ExcludedResourcesTable() [][]string { return rd.resourcesTable(rd.ExludedResources) } func (rd *ReportData) ImpactedTable() [][]string { headers := []string{"Validated Using", "Source", "Category", "Impact", "Resource Type", "Recommendation", "Recommendation Id", "Subscription Id", "Subscription Name", "Resource Group", "Resource Name", "Resource Id", "Param1", "Param2", "Param3", "Param4", "Param5", "Learn"} rows := [][]string{} for _, r := range rd.Aprl { row := []string{ "Azure Resource Graph", r.Source, string(r.Category), string(r.Impact), r.ResourceType, r.Recommendation, r.RecommendationID, MaskSubscriptionID(r.SubscriptionID, rd.Mask), r.SubscriptionName, r.ResourceGroup, r.Name, MaskSubscriptionIDInResourceID(r.ResourceID, rd.Mask), r.Param1, r.Param2, r.Param3, r.Param4, r.Param5, r.Learn, } rows = append(rows, row) } for _, d := range rd.Azqr { for _, r := range d.Recommendations { if r.NotCompliant { row := []string{ "Azure Resource Manager", "AZQR", string(r.Category), string(r.Impact), d.Type, r.Recommendation, r.RecommendationID, MaskSubscriptionID(d.SubscriptionID, rd.Mask), d.SubscriptionName, d.ResourceGroup, d.ServiceName, MaskSubscriptionIDInResourceID(d.ResourceID(), rd.Mask), r.Result, "", "", "", "", r.LearnMoreUrl, } rows = append(rows, row) } } } rows = append([][]string{headers}, rows...) return rows } func (rd *ReportData) CostTable() [][]string { headers := []string{"From", "To", "Subscription Id", "Subscription Name", "Service Name", "Value", "Currency"} rows := [][]string{} for _, r := range rd.Cost.Items { row := []string{ rd.Cost.From.Format("2006-01-02"), rd.Cost.To.Format("2006-01-02"), MaskSubscriptionID(r.SubscriptionID, rd.Mask), r.SubscriptionName, r.ServiceName, r.Value, r.Currency, } rows = append(rows, row) } rows = append([][]string{headers}, rows...) return rows } func (rd *ReportData) DefenderTable() [][]string { headers := []string{"Subscription Id", "Subscription Name", "Name", "Tier"} rows := [][]string{} for _, d := range rd.Defender { row := []string{ MaskSubscriptionID(d.SubscriptionID, rd.Mask), d.SubscriptionName, d.Name, d.Tier, } rows = append(rows, row) } rows = append([][]string{headers}, rows...) return rows } func (rd *ReportData) AdvisorTable() [][]string { headers := []string{"Subscription Id", "Subscription Name", "Resource Type", "Resource Name", "Category", "Impact", "Description", "Resource Id", "Recommendation Id"} rows := [][]string{} for _, d := range rd.Advisor { row := []string{ MaskSubscriptionID(d.SubscriptionID, rd.Mask), d.SubscriptionName, d.Type, d.Name, d.Category, d.Impact, d.Description, MaskSubscriptionIDInResourceID(d.ResourceID, rd.Mask), d.RecommendationID, } rows = append(rows, row) } rows = append([][]string{headers}, rows...) return rows } func (rd *ReportData) RecommendationsTable() [][]string { counter := map[string]int{} for _, rt := range rd.Recommendations { for _, r := range rt { counter[r.RecommendationID] = 0 } } for _, r := range rd.Aprl { counter[r.RecommendationID]++ } for _, d := range rd.Azqr { for _, r := range d.Recommendations { if r.NotCompliant { counter[r.RecommendationID]++ } } } headers := []string{"Implemented", "Number of Impacted Resources", "Azure Service / Well-Architected", "Recommendation Source", "Azure Service Category / Well-Architected Area", "Azure Service / Well-Architected Topic", "Resiliency Category", "Recommendation", "Impact", "Best Practices Guidance", "Read More", "Recommendation Id"} rows := [][]string{} for _, rt := range rd.Recommendations { for _, r := range rt { implemented := counter[r.RecommendationID] == 0 categoryPart := "" servicePart := "" typeParts := strings.Split(r.ResourceType, "/") categoryPart = typeParts[0] if len(typeParts) > 1 { servicePart = typeParts[1] } row := []string{ fmt.Sprintf("%t", implemented), fmt.Sprint(counter[r.RecommendationID]), "Azure Service", r.Source, categoryPart, servicePart, string(r.Category), r.Recommendation, string(r.Impact), r.LongDescription, r.LearnMoreLink[0].Url, r.RecommendationID, } rows = append(rows, row) } } rows = append([][]string{headers}, rows...) return rows } func (rd *ReportData) ResourceTypesTable() [][]string { headers := []string{"Subscription Name", "Resource Type", "Number of Resources", "Available in APRL?", "Custom1", "Custom2", "Custom3"} rows := [][]string{} for _, r := range rd.ResourceTypeCount { row := []string{ r.Subscription, r.ResourceType, fmt.Sprint(r.Count), r.AvailableInAPRL, "", "", "", } rows = append(rows, row) } rows = append([][]string{headers}, rows...) return rows } func (rd *ReportData) DefenderRecommendationsTable() [][]string { headers := []string{"Subscription Id", "Subscription Name", "Resource Group", "Resource Type", "Resource Name", "Category", "Recommendation Severity", "Recommendation Name", "Action Description", "Remediation Description", "AzPortal Link", "Resource Id"} rows := [][]string{} for _, d := range rd.DefenderRecommendations { row := []string{ MaskSubscriptionID(d.SubscriptionId, rd.Mask), d.SubscriptionName, d.ResourceGroupName, d.ResourceType, d.ResourceName, d.Category, d.RecommendationSeverity, d.RecommendationName, d.ActionDescription, d.RemediationDescription, d.AzPortalLink, MaskSubscriptionIDInResourceID(d.ResourceId, rd.Mask), } rows = append(rows, row) } rows = append([][]string{headers}, rows...) return rows } func (rd *ReportData) ResourceIDs() []*string { ids := []*string{} for _, r := range rd.Resources { ids = append(ids, &r.ID) } return ids } func NewReportData(outputFile string, mask bool) ReportData { return ReportData{ OutputFileName: outputFile, Mask: mask, Recommendations: map[string]map[string]models.AprlRecommendation{}, Azqr: []models.AzqrServiceResult{}, Aprl: []models.AprlResult{}, Defender: []models.DefenderResult{}, DefenderRecommendations: []models.DefenderRecommendation{}, Advisor: []models.AdvisorResult{}, Cost: &models.CostResult{ Items: []*models.CostResultItem{}, }, ResourceTypeCount: []models.ResourceTypeCount{}, } } func MaskSubscriptionID(subscriptionID string, mask bool) string { if len(subscriptionID) < 36 { return "" } if !mask { return subscriptionID } // Show only last 7 chars of the subscription ID return fmt.Sprintf("xxxxxxxx-xxxx-xxxx-xxxx-xxxxx%s", subscriptionID[29:]) } func MaskSubscriptionIDInResourceID(resourceID string, mask bool) string { if !strings.HasPrefix(resourceID, "/subscriptions/") { return "" } if !mask { return resourceID } parts := strings.Split(resourceID, "/") parts[2] = MaskSubscriptionID(parts[2], mask) return strings.Join(parts, "/") } func (rd *ReportData) resourcesTable(resources []*models.Resource) [][]string { headers := []string{"Subscription Id", "Resource Group", "Location", "Resource Type", "Resource Name", "Sku Name", "Sku Tier", "Kind", "SLA", "Resource Id"} rows := [][]string{} for _, r := range resources { sla := "" for _, a := range rd.Azqr { if strings.EqualFold(strings.ToLower(a.ResourceID()), strings.ToLower(r.ID)) { for _, rc := range a.Recommendations { if rc.RecommendationType == models.TypeSLA { sla = rc.Result break } } if sla != "" { break } } } row := []string{ MaskSubscriptionID(r.SubscriptionID, rd.Mask), r.ResourceGroup, r.Location, r.Type, r.Name, r.SkuName, r.SkuTier, r.Kind, sla, MaskSubscriptionIDInResourceID(r.ID, rd.Mask), } rows = append(rows, row) } rows = append([][]string{headers}, rows...) return rows }