dnscontrol/commands/printIR.go
Tom Limoncelli b821f4914f
FEATURE: "check" subcommand should send to stdout (#1755)
Co-authored-by: Grant Slater <github@firefishy.com>
2022-09-22 11:03:03 -04:00

173 lines
4.2 KiB
Go

package commands
import (
"encoding/json"
"fmt"
"log"
"os"
"strings"
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/js"
"github.com/StackExchange/dnscontrol/v3/pkg/normalize"
"github.com/urfave/cli/v2"
)
var _ = cmd(catDebug, func() *cli.Command {
var args PrintIRArgs
return &cli.Command{
Name: "print-ir",
Usage: "Output intermediate representation (IR) after running validation and normalization logic.",
Action: func(c *cli.Context) error {
return exit(PrintIR(args))
},
Flags: args.flags(),
}
}())
// CheckArgs encapsulates the flags/arguments for the check command.
type CheckArgs struct {
GetDNSConfigArgs
}
var _ = cmd(catDebug, func() *cli.Command {
var args CheckArgs
// This is the same as print-ir with the following changes:
// - no JSON is output
// - error messages are sent to stdout.
// - prints "No errors." if there were no errors.
return &cli.Command{
Name: "check",
Usage: "Check and validate dnsconfig.js. Output to stdout. Do not access providers.",
Action: func(c *cli.Context) error {
// Create a PrintIRArgs struct and copy our args to the
// appropriate fields.
var pargs PrintIRArgs
// Copy these verbatim:
pargs.JSFile = args.JSFile
pargs.JSONFile = args.JSONFile
pargs.DevMode = args.DevMode
pargs.Variable = args.Variable
// Force these settings:
pargs.Pretty = false
pargs.Output = os.DevNull
// "check" sends all errors to stdout, not stderr.
cli.ErrWriter = os.Stdout
log.SetOutput(os.Stdout)
err := exit(PrintIR(pargs))
if err == nil {
fmt.Fprintf(os.Stdout, "No errors.\n")
}
return err
},
Flags: args.flags(),
}
}())
// PrintIRArgs encapsulates the flags/arguments for the print-ir command.
type PrintIRArgs struct {
GetDNSConfigArgs
PrintJSONArgs
Raw bool
}
func (args *PrintIRArgs) flags() []cli.Flag {
flags := append(args.GetDNSConfigArgs.flags(), args.PrintJSONArgs.flags()...)
flags = append(flags, &cli.BoolFlag{
Name: "raw",
Usage: "Skip validation and normalization. Just print js result.",
Destination: &args.Raw,
})
return flags
}
// PrintIR implements the print-ir subcommand.
func PrintIR(args PrintIRArgs) error {
cfg, err := GetDNSConfig(args.GetDNSConfigArgs)
if err != nil {
return err
}
if !args.Raw {
errs := normalize.ValidateAndNormalizeConfig(cfg)
if PrintValidationErrors(errs) {
return fmt.Errorf("exiting due to validation errors")
}
}
return PrintJSON(args.PrintJSONArgs, cfg)
}
// PrintValidationErrors formats and prints the validation errors and warnings.
func PrintValidationErrors(errs []error) (fatal bool) {
if len(errs) == 0 {
return false
}
log.Printf("%d Validation errors:\n", len(errs))
for _, err := range errs {
if _, ok := err.(normalize.Warning); ok {
log.Printf("WARNING: %s\n", err)
} else {
fatal = true
log.Printf("ERROR: %s\n", err)
}
}
return
}
// ExecuteDSL executes the dnsconfig.js contents.
func ExecuteDSL(args ExecuteDSLArgs) (*models.DNSConfig, error) {
if args.JSFile == "" {
return nil, fmt.Errorf("no config specified")
}
dnsConfig, err := js.ExecuteJavascript(args.JSFile, args.DevMode, stringSliceToMap(args.Variable))
if err != nil {
return nil, fmt.Errorf("executing %s: %w", args.JSFile, err)
}
return dnsConfig, nil
}
// PrintJSON outputs/prettyprints the IR data.
func PrintJSON(args PrintJSONArgs, config *models.DNSConfig) (err error) {
var dat []byte
if args.Pretty {
dat, err = json.MarshalIndent(config, "", " ")
} else {
dat, err = json.Marshal(config)
}
if err != nil {
return err
}
if args.Output != "" {
f, err := os.Create(args.Output)
if err != nil {
return err
}
defer f.Close()
_, err = f.Write(dat)
return err
}
fmt.Println(string(dat))
return nil
}
func exit(err error) error {
if err == nil {
return nil
}
return cli.Exit(err, 1)
}
// stringSliceToMap converts cli.StringSlice to map[string]string for further processing
func stringSliceToMap(stringSlice cli.StringSlice) map[string]string {
mapString := make(map[string]string, len(stringSlice.Value()))
for _, values := range stringSlice.Value() {
parts := strings.SplitN(values, "=", 2)
if len(parts) == 2 {
mapString[parts[0]] = parts[1]
}
}
return mapString
}