diff --git a/commands/ppreviewPush.go b/commands/ppreviewPush.go index c394819a0..2cfe1ef75 100644 --- a/commands/ppreviewPush.go +++ b/commands/ppreviewPush.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "os" + "regexp" "strings" "sync" "sync/atomic" @@ -300,7 +301,7 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor if !skip { totalCorrections += len(corrections) out.EndProvider2(provider.Name, len(corrections)) - reportItems = append(reportItems, genReportItem(zone.Name, corrections, provider.Name)) + reportItems = append(reportItems, genReportItem(zone.Name, corrections, provider.Name, "")) anyErrors = cmp.Or(anyErrors, pprintOrRunCorrections(zone.Name, provider.Name, corrections, out, push || args.PopulateOnPreview, interactive, notifier, report)) } } @@ -367,7 +368,9 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor numActions := zone.GetChangeCount(provider.Name) totalCorrections += numActions out.EndProvider2(provider.Name, numActions) - reportItems = append(reportItems, genReportItem(zone.Name, corrections, provider.Name)) + numCorrections := len(corrections) + out.Printf("numActions: %d \nnumCorrections: %d\n", totalCorrections, numCorrections) + reportItems = append(reportItems, genReportItem(zone.Name, corrections, provider.Name, "")) anyErrors = cmp.Or(anyErrors, pprintOrRunCorrections(zone.Name, provider.Name, corrections, out, push, interactive, notifier, report)) } } @@ -380,7 +383,7 @@ func prun(args PPreviewArgs, push bool, interactive bool, out printer.CLI, repor numActions := zone.GetChangeCount(zone.RegistrarInstance.Name) out.EndProvider2(zone.RegistrarName, numActions) totalCorrections += numActions - reportItems = append(reportItems, genReportItem(zone.Name, corrections, zone.RegistrarName)) + reportItems = append(reportItems, genReportItem(zone.Name, corrections, "", zone.RegistrarName)) anyErrors = cmp.Or(anyErrors, pprintOrRunCorrections(zone.Name, zone.RegistrarInstance.Name, corrections, out, push, interactive, notifier, report)) } } @@ -564,19 +567,44 @@ func skipProvider(name string, providers []*models.DNSProviderInstance) bool { }) } -func genReportItem(zname string, corrections []*models.Correction, pname string) *ReportItem { +func parseCorrectionMsg(s string) []string { + ansiRe := regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`) + s = ansiRe.ReplaceAllString(s, "") + corrections := strings.Split(s, "\n") + + // Clean up the slice, precaution remove any empty entries. + clean := make([]string, 0, len(corrections)) + for _, l := range corrections { + l = strings.TrimSpace(l) + if l != "" { + clean = append(clean, l) + } + } + return clean +} + +func genReportItem(zoneName string, corrections []*models.Correction, providerName string, registrarName string) *ReportItem { // Only count the actions, not the messages. + // From my testing corrections is always just contains 1 connection, and Msg is concat of all the changes cnt := 0 + correctionDetails := make([]string, 0) for _, cor := range corrections { if cor.F != nil { - cnt++ + cnt++ // This seems to always be 1, so we will get the number of corrections from len(correctionDetails) + correctionDetails = append(correctionDetails, parseCorrectionMsg(cor.Msg)...) } } r := ReportItem{ - Domain: zname, - Corrections: cnt, - Provider: pname, + Domain: zoneName, + Corrections: len(correctionDetails), + CorrectionDetails: correctionDetails, + } + if providerName != "" { + r.Provider = providerName + } + if registrarName != "" { + r.Registrar = registrarName } return &r } @@ -635,11 +663,13 @@ func writeReport(report string, reportItems []*ReportItem) error { return err } defer f.Close() - b, err := json.MarshalIndent(reportItems, "", " ") - if err != nil { - return err - } - if _, err := f.Write(b); err != nil { + + // Disabling HTML encoding + enc := json.NewEncoder(f) + enc.SetIndent("", " ") + enc.SetEscapeHTML(false) + + if err := enc.Encode(reportItems); err != nil { return err } return nil diff --git a/commands/previewPush.go b/commands/previewPush.go index 17c42d75d..e5119f000 100644 --- a/commands/previewPush.go +++ b/commands/previewPush.go @@ -12,10 +12,11 @@ import ( // ReportItem is a record of corrections for a particular domain/provider/registrar. type ReportItem struct { - Domain string `json:"domain"` - Corrections int `json:"corrections"` - Provider string `json:"provider,omitempty"` - Registrar string `json:"registrar,omitempty"` + Domain string `json:"domain"` + Corrections int `json:"corrections"` + CorrectionDetails []string `json:"correction_details,omitempty"` + Provider string `json:"provider,omitempty"` + Registrar string `json:"registrar,omitempty"` } // InitializeProviders takes (fully processed) configuration and instantiates all providers and returns them. diff --git a/documentation/advanced-features/json-reports.md b/documentation/advanced-features/json-reports.md index 0844a9847..7a0424174 100644 --- a/documentation/advanced-features/json-reports.md +++ b/documentation/advanced-features/json-reports.md @@ -2,14 +2,14 @@ DNSControl can generate a machine-parseable report of changes. -The report is JSON formatted and contains the zonename, the provider or -registrar name, and the number of changes. +The report is JSON-formatted and contains the zonename, the provider or +registrar name, the number of changes (corrections), and the correction details. +All values are in text, values may contain `<`,`>` and `&` escape as needed. To generate the report, add the `--report ` option to a preview or push command (this includes `preview`, `ppreview`, `push`, `ppush`). - The report lists the changes that would be (preview) or are (push) attempted, whether they are successful or not. @@ -23,6 +23,18 @@ If a fatal error happens during the run, no report is generated. { "domain": "private.example.com", "corrections": 10, + "correction_details": [ + "± MODIFY private.example.com A (1.1.1.1 ttl=60) -> (1.1.1.6 ttl=300)", + "+ CREATE private.example.com A 1.1.1.7 ttl=300", + "± MODIFY-TTL private.example.com TXT \"v=spf1 include:spf.protection.outlook.com -all\" ttl=(60->300)", + "+ CREATE private.example.com TXT \"v=DKIM1; k=rsa; p=xxxx....xxx\" ttl=300", + "+ CREATE private.example.com MX 0 private-example-com.mail.protection.outlook.com. ttl=300", + "+ CREATE *.private.example.com A 1.1.1.6 ttl=300", + "+ CREATE *.private.example.com A 1.1.1.7 ttl=300", + "+ CREATE ns101.private.example.com A 1.1.1.1 ttl=300", + "+ CREATE ns102.private.example.com A 1.0.0.2 ttl=300", + "- DELETE out-of-band.private.example.com TXT \"This out-of-band TXT record should be removed.\" ttl=300" + ], "provider": "bind" }, { @@ -33,6 +45,13 @@ If a fatal error happens during the run, no report is generated. { "domain": "admin.example.com", "corrections": 5, + "correction_details": [ + "± MODIFY admin.example.com A (1.1.1.1 ttl=60) -> (1.1.1.6 ttl=300)", + "+ CREATE admin.example.com A 1.1.1.7 ttl=300", + "± MODIFY-TTL admin.example.com TXT \"v=spf1 include:spf.protection.outlook.com -all\" ttl=(60->300)", + "+ CREATE admin.example.com TXT \"v=DKIM1; k=rsa; p=xxxx....xxx\" ttl=300", + "- DELETE out-of-band.admin.example.com TXT \"This out-of-band TXT record should be removed.\" ttl=300" + ], "provider": "bind" }, {