github.com/StackExchange/DNSControl@v0.2.8/commands/previewPush.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  
     8  	"github.com/StackExchange/dnscontrol/models"
     9  	"github.com/StackExchange/dnscontrol/pkg/nameservers"
    10  	"github.com/StackExchange/dnscontrol/pkg/normalize"
    11  	"github.com/StackExchange/dnscontrol/pkg/notifications"
    12  	"github.com/StackExchange/dnscontrol/pkg/printer"
    13  	"github.com/StackExchange/dnscontrol/providers"
    14  	"github.com/StackExchange/dnscontrol/providers/config"
    15  	"github.com/pkg/errors"
    16  	"github.com/urfave/cli"
    17  )
    18  
    19  var _ = cmd(catMain, func() *cli.Command {
    20  	var args PreviewArgs
    21  	return &cli.Command{
    22  		Name:  "preview",
    23  		Usage: "read live configuration and identify changes to be made, without applying them",
    24  		Action: func(ctx *cli.Context) error {
    25  			return exit(Preview(args))
    26  		},
    27  		Flags: args.flags(),
    28  	}
    29  }())
    30  
    31  // PreviewArgs contains all data/flags needed to run preview, independently of CLI
    32  type PreviewArgs struct {
    33  	GetDNSConfigArgs
    34  	GetCredentialsArgs
    35  	FilterArgs
    36  	Notify bool
    37  }
    38  
    39  func (args *PreviewArgs) flags() []cli.Flag {
    40  	flags := args.GetDNSConfigArgs.flags()
    41  	flags = append(flags, args.GetCredentialsArgs.flags()...)
    42  	flags = append(flags, args.FilterArgs.flags()...)
    43  	flags = append(flags, cli.BoolFlag{
    44  		Name:        "notify",
    45  		Destination: &args.Notify,
    46  		Usage:       `set to true to send notifications to configured destinations`,
    47  	})
    48  	return flags
    49  }
    50  
    51  var _ = cmd(catMain, func() *cli.Command {
    52  	var args PushArgs
    53  	return &cli.Command{
    54  		Name:  "push",
    55  		Usage: "identify changes to be made, and perform them",
    56  		Action: func(ctx *cli.Context) error {
    57  			return exit(Push(args))
    58  		},
    59  		Flags: args.flags(),
    60  	}
    61  }())
    62  
    63  // PushArgs contains all data/flags needed to run push, independently of CLI
    64  type PushArgs struct {
    65  	PreviewArgs
    66  	Interactive bool
    67  }
    68  
    69  func (args *PushArgs) flags() []cli.Flag {
    70  	flags := args.PreviewArgs.flags()
    71  	flags = append(flags, cli.BoolFlag{
    72  		Name:        "i",
    73  		Destination: &args.Interactive,
    74  		Usage:       "Interactive. Confirm or Exclude each correction before they run",
    75  	})
    76  	return flags
    77  }
    78  
    79  // Preview implements the preview subcommand.
    80  func Preview(args PreviewArgs) error {
    81  	return run(args, false, false, printer.DefaultPrinter)
    82  }
    83  
    84  // Push implements the push subcommand.
    85  func Push(args PushArgs) error {
    86  	return run(args.PreviewArgs, true, args.Interactive, printer.DefaultPrinter)
    87  }
    88  
    89  // run is the main routine common to preview/push
    90  func run(args PreviewArgs, push bool, interactive bool, out printer.CLI) error {
    91  	// TODO: make truly CLI independent. Perhaps return results on a channel as they occur
    92  	cfg, err := GetDNSConfig(args.GetDNSConfigArgs)
    93  	if err != nil {
    94  		return err
    95  	}
    96  	errs := normalize.NormalizeAndValidateConfig(cfg)
    97  	if PrintValidationErrors(errs) {
    98  		return errors.Errorf("Exiting due to validation errors")
    99  	}
   100  	// TODO:
   101  	notifier, err := InitializeProviders(args.CredsFile, cfg, args.Notify)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	anyErrors := false
   106  	totalCorrections := 0
   107  DomainLoop:
   108  	for _, domain := range cfg.Domains {
   109  		if !args.shouldRunDomain(domain.Name) {
   110  			continue
   111  		}
   112  		out.StartDomain(domain.Name)
   113  		nsList, err := nameservers.DetermineNameservers(domain)
   114  		if err != nil {
   115  			return err
   116  		}
   117  		domain.Nameservers = nsList
   118  		nameservers.AddNSRecords(domain)
   119  		for _, provider := range domain.DNSProviderInstances {
   120  			dc, err := domain.Copy()
   121  			if err != nil {
   122  				return err
   123  			}
   124  			shouldrun := args.shouldRunProvider(provider.Name, dc)
   125  			out.StartDNSProvider(provider.Name, !shouldrun)
   126  			if !shouldrun {
   127  				continue
   128  			}
   129  			corrections, err := provider.Driver.GetDomainCorrections(dc)
   130  			out.EndProvider(len(corrections), err)
   131  			if err != nil {
   132  				anyErrors = true
   133  				continue DomainLoop
   134  			}
   135  			totalCorrections += len(corrections)
   136  			anyErrors = printOrRunCorrections(domain.Name, provider.Name, corrections, out, push, interactive, notifier) || anyErrors
   137  		}
   138  		run := args.shouldRunProvider(domain.RegistrarName, domain)
   139  		out.StartRegistrar(domain.RegistrarName, !run)
   140  		if !run {
   141  			continue
   142  		}
   143  		if len(domain.Nameservers) == 0 && domain.Metadata["no_ns"] != "true" {
   144  			out.Warnf("No nameservers declared; skipping registrar. Add {no_ns:'true'} to force.\n")
   145  			continue
   146  		}
   147  		dc, err := domain.Copy()
   148  		if err != nil {
   149  			log.Fatal(err)
   150  		}
   151  		corrections, err := domain.RegistrarInstance.Driver.GetRegistrarCorrections(dc)
   152  		out.EndProvider(len(corrections), err)
   153  		if err != nil {
   154  			anyErrors = true
   155  			continue
   156  		}
   157  		totalCorrections += len(corrections)
   158  		anyErrors = printOrRunCorrections(domain.Name, domain.RegistrarName, corrections, out, push, interactive, notifier) || anyErrors
   159  	}
   160  	if os.Getenv("TEAMCITY_VERSION") != "" {
   161  		fmt.Fprintf(os.Stderr, "##teamcity[buildStatus status='SUCCESS' text='%d corrections']", totalCorrections)
   162  	}
   163  	notifier.Done()
   164  	out.Printf("Done. %d corrections.\n", totalCorrections)
   165  	if anyErrors {
   166  		return errors.Errorf("Completed with errors")
   167  	}
   168  	return nil
   169  }
   170  
   171  // InitializeProviders takes a creds file path and a DNSConfig object. Creates all providers with the proper types, and returns them.
   172  // nonDefaultProviders is a list of providers that should not be run unless explicitly asked for by flags.
   173  func InitializeProviders(credsFile string, cfg *models.DNSConfig, notifyFlag bool) (notify notifications.Notifier, err error) {
   174  	var providerConfigs map[string]map[string]string
   175  	var notificationCfg map[string]string
   176  	defer func() {
   177  		notify = notifications.Init(notificationCfg)
   178  	}()
   179  	providerConfigs, err = config.LoadProviderConfigs(credsFile)
   180  	if err != nil {
   181  		return
   182  	}
   183  	if notifyFlag {
   184  		notificationCfg = providerConfigs["notifications"]
   185  	}
   186  	isNonDefault := map[string]bool{}
   187  	for name, vals := range providerConfigs {
   188  		// add "_exclude_from_defaults":"true" to a provider to exclude it from being run unless
   189  		// -providers=all or -providers=name
   190  		if vals["_exclude_from_defaults"] == "true" {
   191  			isNonDefault[name] = true
   192  		}
   193  	}
   194  	registrars := map[string]providers.Registrar{}
   195  	dnsProviders := map[string]providers.DNSServiceProvider{}
   196  	for _, d := range cfg.Domains {
   197  		if registrars[d.RegistrarName] == nil {
   198  			rCfg := cfg.RegistrarsByName[d.RegistrarName]
   199  			r, err := providers.CreateRegistrar(rCfg.Type, providerConfigs[d.RegistrarName])
   200  			if err != nil {
   201  				return nil, err
   202  			}
   203  			registrars[d.RegistrarName] = r
   204  		}
   205  		d.RegistrarInstance.Driver = registrars[d.RegistrarName]
   206  		d.RegistrarInstance.IsDefault = !isNonDefault[d.RegistrarName]
   207  		for _, pInst := range d.DNSProviderInstances {
   208  			if dnsProviders[pInst.Name] == nil {
   209  				dCfg := cfg.DNSProvidersByName[pInst.Name]
   210  				prov, err := providers.CreateDNSProvider(dCfg.Type, providerConfigs[dCfg.Name], dCfg.Metadata)
   211  				if err != nil {
   212  					return nil, err
   213  				}
   214  				dnsProviders[pInst.Name] = prov
   215  			}
   216  			pInst.Driver = dnsProviders[pInst.Name]
   217  			pInst.IsDefault = !isNonDefault[pInst.Name]
   218  		}
   219  	}
   220  	return
   221  }
   222  
   223  func printOrRunCorrections(domain string, provider string, corrections []*models.Correction, out printer.CLI, push bool, interactive bool, notifier notifications.Notifier) (anyErrors bool) {
   224  	anyErrors = false
   225  	if len(corrections) == 0 {
   226  		return false
   227  	}
   228  	for i, correction := range corrections {
   229  		out.PrintCorrection(i, correction)
   230  		var err error
   231  		if push {
   232  			if interactive && !out.PromptToRun() {
   233  				continue
   234  			}
   235  			err = correction.F()
   236  			out.EndCorrection(err)
   237  			if err != nil {
   238  				anyErrors = true
   239  			}
   240  		}
   241  		notifier.Notify(domain, provider, correction.Msg, err, !push)
   242  	}
   243  	return anyErrors
   244  }