feat(report): add correction details to json report

- fix correction count 
- fix provider and registrar name to report 
- disable HTMLEscape when writing the report
- update docs
This commit is contained in:
Kevin Neufeld 2025-11-06 10:31:33 -08:00
parent 71c2cc24ca
commit 6ea71d50f7
3 changed files with 70 additions and 20 deletions

View file

@ -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

View file

@ -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.

View file

@ -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 <filename>` 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"
},
{