dnscontrol/pkg/printer/printer.go
Tom Limoncelli 1b2f5d4d34
BUGFIX: IDN support is broken for domain names (#3845)
# Issue

Fixes https://github.com/StackExchange/dnscontrol/issues/3842

CC @das7pad

# Resolution

Convert domain.Name to IDN earlier in the pipeline. Hack the --domains
processing to convert everything to IDN.

* Domain names are now stored 3 ways: The original input from
dnsconfig.js, canonical IDN format (`xn--...`), and Unicode format. All
are downcased. Providers that haven't been updated will receive the IDN
format instead of the original input format. This might break some
providers but only for users with unicode in their D("domain.tld").
PLEASE TEST YOUR PROVIDER.
* BIND filename formatting options have been added to access the new
formats.

# Breaking changes

* BIND zonefiles may change. The default used the name input in the D()
statement. It now defaults to the IDN name + "!tag" if there is a tag.
* Providers that are not IDN-aware may break (hopefully only if they
weren't processing IDN already)

---------

Co-authored-by: Jakob Ackermann <das7pad@outlook.com>
2025-11-29 12:17:44 -05:00

220 lines
6.1 KiB
Go

package printer
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"github.com/StackExchange/dnscontrol/v4/models"
)
// CLI is an abstraction around the CLI.
type CLI interface {
Printer
StartDomain(dc *models.DomainConfig)
StartDNSProvider(name string, skip bool)
EndProvider(name string, numCorrections int, err error)
EndProvider2(name string, numCorrections int)
StartRegistrar(name string, skip bool)
PrintCorrection(n int, c *models.Correction)
PrintReport(n int, c *models.Correction) // Print corrections that are diff2.REPORT
EndCorrection(err error)
PromptToRun() bool
}
// Printer is a simple abstraction for printing data. Can be passed to providers to give simple output capabilities.
type Printer interface {
Debugf(fmt string, args ...interface{})
Printf(fmt string, args ...interface{})
Println(lines ...string)
Warnf(fmt string, args ...interface{})
Errorf(fmt string, args ...interface{})
PrintfIf(prnt bool, fmt string, args ...interface{})
}
// Debugf is called to print/format debug information.
func Debugf(fmt string, args ...interface{}) {
DefaultPrinter.Debugf(fmt, args...)
}
// Printf is called to print/format information.
func Printf(fmt string, args ...interface{}) {
DefaultPrinter.Printf(fmt, args...)
}
// Println is called to print/format information.
func Println(lines ...string) {
DefaultPrinter.Println(lines...)
}
// Warnf is called to print/format a warning.
func Warnf(fmt string, args ...interface{}) {
DefaultPrinter.Warnf(fmt, args...)
}
// Errorf is called to print/format an error.
// func Errorf(fmt string, args ...interface{}) {
// DefaultPrinter.Errorf(fmt, args...)
// }
// PrintfIf is called to optionally print something.
func PrintfIf(prnt bool, fmt string, args ...interface{}) {
DefaultPrinter.PrintfIf(prnt, fmt, args...)
}
// DefaultPrinter is the default Printer, used by Debugf, Printf, and Warnf.
var DefaultPrinter = &ConsolePrinter{
Reader: bufio.NewReader(os.Stdin),
Writer: os.Stdout,
Verbose: false,
}
// SkinnyReport is true to disable certain print statements.
// This is a hack until we have the new printer replacement. The long
// variable name is easy to grep for when we make the conversion.
var SkinnyReport = true
// MaxReport represents how many records to show if SkinnyReport == true
var MaxReport = 5
// ConsolePrinter is a handle for the console printer.
type ConsolePrinter struct {
Reader *bufio.Reader
Writer io.Writer
Verbose bool
}
// StartDomain is called at the start of each domain.
func (c ConsolePrinter) StartDomain(dc *models.DomainConfig) {
if dc.Name == dc.NameUnicode {
fmt.Fprintf(c.Writer, "******************** Domain: %s\n", dc.Name)
} else {
fmt.Fprintf(c.Writer, "******************** Domain: %s (%s)\n", dc.NameUnicode, dc.Name)
}
}
// PrintCorrection is called to print/format each correction.
func (c ConsolePrinter) PrintCorrection(i int, correction *models.Correction) {
fmt.Fprintf(c.Writer, "#%d: %s\n", i+1, correction.Msg)
}
// PrintReport is called to print/format each non-mutating correction (diff2.REPORT).
func (c ConsolePrinter) PrintReport(i int, correction *models.Correction) {
fmt.Fprintf(c.Writer, "INFO#%d: %s\n", i+1, correction.Msg)
}
// PromptToRun prompts the user to see if they want to execute a correction.
func (c ConsolePrinter) PromptToRun() bool {
fmt.Fprint(c.Writer, "Run? (y/N): ")
txt, err := c.Reader.ReadString('\n')
run := true
if err != nil {
run = false
}
txt = strings.ToLower(strings.TrimSpace(txt))
if txt != "y" {
run = false
}
if !run {
fmt.Fprintln(c.Writer, "Skipping")
}
return run
}
// EndCorrection is called at the end of each correction.
func (c ConsolePrinter) EndCorrection(err error) {
if err != nil {
fmt.Fprintln(c.Writer, "FAILURE!", err)
} else {
fmt.Fprintln(c.Writer, "SUCCESS!")
}
}
// StartDNSProvider is called at the start of each new provider.
func (c ConsolePrinter) StartDNSProvider(provider string, skip bool) {
lbl := ""
if skip {
lbl = " (skipping)"
}
if !SkinnyReport {
fmt.Fprintf(c.Writer, "----- DNS Provider: %s...%s\n", provider, lbl)
}
}
// StartRegistrar is called at the start of each new registrar.
func (c ConsolePrinter) StartRegistrar(provider string, skip bool) {
lbl := ""
if skip {
lbl = " (skipping)"
}
if !SkinnyReport {
fmt.Fprintf(c.Writer, "----- Registrar: %s...%s\n", provider, lbl)
}
}
// EndProvider is called at the end of each provider.
func (c ConsolePrinter) EndProvider(name string, numCorrections int, err error) {
if err != nil {
fmt.Fprintln(c.Writer, "ERROR")
fmt.Fprintf(c.Writer, "Error getting corrections (%s): %s\n", name, err)
} else {
plural := "s"
if numCorrections == 1 {
plural = ""
}
if (SkinnyReport) && (numCorrections == 0) {
return
}
fmt.Fprintf(c.Writer, "%d correction%s (%s)\n", numCorrections, plural, name)
}
}
// EndProvider2 is called at the end of each provider.
func (c ConsolePrinter) EndProvider2(name string, numCorrections int) {
plural := "s"
if numCorrections == 1 {
plural = ""
}
if (SkinnyReport) && (numCorrections == 0) {
return
}
fmt.Fprintf(c.Writer, "%d correction%s (%s)\n", numCorrections, plural, name)
}
// Debugf is called to print/format debug information.
func (c ConsolePrinter) Debugf(format string, args ...interface{}) {
if c.Verbose {
fmt.Fprintf(c.Writer, format, args...)
}
}
// Printf is called to print/format information.
func (c ConsolePrinter) Printf(format string, args ...interface{}) {
fmt.Fprintf(c.Writer, format, args...)
}
// Println is called to print/format information.
func (c ConsolePrinter) Println(lines ...string) {
fmt.Fprintln(c.Writer, lines)
}
// Warnf is called to print/format a warning.
func (c ConsolePrinter) Warnf(format string, args ...interface{}) {
fmt.Fprintf(c.Writer, "WARNING: "+format, args...)
}
// Errorf is called to print/format an error.
func (c ConsolePrinter) Errorf(format string, args ...interface{}) {
fmt.Fprintf(c.Writer, "ERROR: "+format, args...)
}
// PrintfIf is called to optionally print/format a message.
func (c ConsolePrinter) PrintfIf(prnt bool, format string, args ...interface{}) {
if prnt {
fmt.Fprintf(c.Writer, format, args...)
}
}