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 }