forked from StackExchange/dnscontrol
-
Notifications
You must be signed in to change notification settings - Fork 1
/
commands.go
277 lines (255 loc) · 7 KB
/
commands.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
package commands
import (
"encoding/json"
"fmt"
"os"
"sort"
"strings"
"github.com/StackExchange/dnscontrol/models"
"github.com/StackExchange/dnscontrol/pkg/printer"
"github.com/pkg/errors"
"github.com/urfave/cli"
)
// categories of commands
const (
catMain = "\b main" // screwed up to alphebatize first
catDebug = "debug"
catUtils = "utility"
)
var commands = []cli.Command{}
var version string
func cmd(cat string, c *cli.Command) bool {
c.Category = cat
commands = append(commands, *c)
return true
}
var _ = cmd(catDebug, &cli.Command{
Name: "version",
Usage: "Print version information",
Action: func(c *cli.Context) {
fmt.Println(version)
},
})
// Run will execute the CLI
func Run(v string) int {
version = v
app := cli.NewApp()
app.Version = version
app.Name = "dnscontrol"
app.HideVersion = true
app.Usage = "dnscontrol is a compiler and DSL for managing dns zones"
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "v",
Usage: "Enable detailed logging",
Destination: &printer.DefaultPrinter.Verbose,
},
}
sort.Sort(cli.CommandsByName(commands))
app.Commands = commands
app.EnableBashCompletion = true
if err := app.Run(os.Args); err != nil {
return 1
}
return 0
}
// Shared config types
// GetDNSConfigArgs contains what we need to get a valid dns config.
// Could come from parsing js, or from stored json
type GetDNSConfigArgs struct {
ExecuteDSLArgs
JSONFile string
}
func (args *GetDNSConfigArgs) flags() []cli.Flag {
return append(args.ExecuteDSLArgs.flags(),
cli.StringFlag{
Destination: &args.JSONFile,
Name: "ir",
Usage: "Read IR (json) directly from this file. Do not process DSL at all",
},
cli.StringFlag{
Destination: &args.JSONFile,
Name: "json",
Hidden: true,
Usage: "same as -ir. only here for backwards compatibility, hence hidden",
},
)
}
// GetDNSConfig reads the json-formatted IR file. Or executes javascript. All depending on flags provided.
func GetDNSConfig(args GetDNSConfigArgs) (*models.DNSConfig, error) {
if args.JSONFile != "" {
f, err := os.Open(args.JSONFile)
if err != nil {
return nil, err
}
defer f.Close()
dec := json.NewDecoder(f)
cfg := &models.DNSConfig{}
if err = dec.Decode(cfg); err != nil {
return nil, err
}
return preloadProviders(cfg, nil)
}
return preloadProviders(ExecuteDSL(args.ExecuteDSLArgs))
}
// the json only contains provider names inside domains. This denormalizes the data for more
// convenient access patterns. Does everything we need to prepare for the validation phase, but
// cannot do anything that requires the credentials file yet.
func preloadProviders(cfg *models.DNSConfig, err error) (*models.DNSConfig, error) {
if err != nil {
return cfg, err
}
//build name to type maps
cfg.RegistrarsByName = map[string]*models.RegistrarConfig{}
cfg.DNSProvidersByName = map[string]*models.DNSProviderConfig{}
for _, reg := range cfg.Registrars {
cfg.RegistrarsByName[reg.Name] = reg
}
for _, p := range cfg.DNSProviders {
cfg.DNSProvidersByName[p.Name] = p
}
// make registrar and dns provider shims. Include name, type, and other metadata, but can't inatantiate
// driver until we load creds in later
for _, d := range cfg.Domains {
reg, ok := cfg.RegistrarsByName[d.RegistrarName]
if !ok {
return nil, errors.Errorf("Registrar named %s expected for %s, but never registered", d.RegistrarName, d.Name)
}
d.RegistrarInstance = &models.RegistrarInstance{
ProviderBase: models.ProviderBase{
Name: reg.Name,
ProviderType: reg.Type,
},
}
for pName, n := range d.DNSProviderNames {
prov, ok := cfg.DNSProvidersByName[pName]
if !ok {
return nil, errors.Errorf("DNS Provider named %s expected for %s, but never registered", pName, d.Name)
}
d.DNSProviderInstances = append(d.DNSProviderInstances, &models.DNSProviderInstance{
ProviderBase: models.ProviderBase{
Name: pName,
ProviderType: prov.Type,
},
NumberOfNameservers: n,
})
}
// sort so everything is deterministic
sort.Slice(d.DNSProviderInstances, func(i, j int) bool {
return d.DNSProviderInstances[i].Name < d.DNSProviderInstances[j].Name
})
}
return cfg, nil
}
// ExecuteDSLArgs are used anytime we need to read and execute dnscontrol DSL
type ExecuteDSLArgs struct {
JSFile string
JSONFile string
DevMode bool
}
func (args *ExecuteDSLArgs) flags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "config",
Value: "dnsconfig.js",
Destination: &args.JSFile,
Usage: "File containing dns config in javascript DSL",
},
cli.StringFlag{
Name: "js",
Value: "dnsconfig.js",
Hidden: true,
Destination: &args.JSFile,
Usage: "same as config. for back compatibility",
},
cli.BoolFlag{
Name: "dev",
Destination: &args.DevMode,
Usage: "Use helpers.js from disk instead of embedded copy",
},
}
}
// PrintJSONArgs are used anytime a command may print some json
type PrintJSONArgs struct {
Pretty bool
Output string
}
func (args *PrintJSONArgs) flags() []cli.Flag {
return []cli.Flag{
cli.BoolFlag{
Name: "pretty",
Destination: &args.Pretty,
Usage: "Pretty print IR JSON",
},
cli.StringFlag{
Name: "out",
Destination: &args.Output,
Usage: "File to write IR JSON to (default stdout)",
},
}
}
// GetCredentialsArgs encapsulates the flags/args for sub-commands that use the creds.json file.
type GetCredentialsArgs struct {
CredsFile string
}
func (args *GetCredentialsArgs) flags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "creds",
Destination: &args.CredsFile,
Usage: "Provider credentials JSON file",
Value: "creds.json",
},
}
}
// FilterArgs encapsulates the flags/args for sub-commands that can filter by provider or domain.
type FilterArgs struct {
Providers string
Domains string
}
func (args *FilterArgs) flags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
Name: "providers",
Destination: &args.Providers,
Usage: `Providers to enable (comma separated list); default is all. Can exclude individual providers from default by adding '"_exclude_from_defaults": "true"' to the credentials file for a provider`,
Value: "",
},
cli.StringFlag{
Name: "domains",
Destination: &args.Domains,
Usage: `Comma separated list of domain names to include`,
Value: "",
},
}
}
func (args *FilterArgs) shouldRunProvider(name string, dc *models.DomainConfig) bool {
if args.Providers == "all" {
return true
}
if args.Providers == "" {
for _, pri := range dc.DNSProviderInstances {
if pri.Name == name {
return pri.IsDefault
}
}
return true
}
for _, prov := range strings.Split(args.Providers, ",") {
if prov == name {
return true
}
}
return false
}
func (args *FilterArgs) shouldRunDomain(d string) bool {
if args.Domains == "" {
return true
}
for _, dom := range strings.Split(args.Domains, ",") {
if dom == d {
return true
}
}
return false
}