-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstates.go
222 lines (188 loc) · 6.88 KB
/
states.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
package main
import (
"fmt"
. "github.com/dirtman/sitepkg"
)
/*****************************************************************************\
Define a few funcs and "wrapper" objects to fetch and hold the results of
Infoblox WAPI Get requests for the entire set of name/data pairs provided by
the user.
Using these funcs/objects is not so useful for user Get requests themselves,
but rather for user Add requests. They can be used to check for the
existence of possibly conflicting records before making any new Add requests.
While adding an A record with the same name as an existing Host record is
certainly allowed by Infoblox and may certainly be what is desired, I find it
beneficial to alert the user just in case. Furthermore, it may be desirable
to not add any records if one or more of the requested records will not be
added due to a record conflict or bad user input. This is where these record
"state" objects come into play. They can be used to hold the current state
of affairs before taking any actions at all. While this -drastically-
increases the overhead and complexity needed to simply add a record, I think
its worth the effort to possibly avoid DNS related fiascoes.
StatesXYX is a string map of pointers to StateXYX. The map keys are the
name/data pairs provided by the user. The key values (the StateXYX pointers)
hold the records retrieved from the WAPI for that name/data key, as well as
an error to track if an error occurred with a WAPI Get request for the
name/data pair.
Update:
In a attempt to reduce the amount of code required with each new record
type to be supported, I defined a "Fetcher" interface, and I separated the
fetching of the raw JSON results from the unmarshalling of those results. I
am not very happy with the results - I think there's most likely a better
approach. But for now I need to move on.
\*****************************************************************************/
type IBObject interface {
GetObjectType() string
GetNDKeys() (string, string)
}
type Fetcher interface {
IBObject
GetNDPairs() []string
GetError(nameData string) error
GetRecordCount(nameData string) int
AddRecords(nameData string, body []byte) error
SetError(nameData string, err error)
NewState(nameData string)
}
type StatesA map[string]*StateA
type StatesHost map[string]*StateHost
type StatesPTR map[string]*StatePTR
type StatesCNAME map[string]*StateCNAME
type StatesAlias map[string]*StateAlias
type StatesFixedAddress map[string]*StateFixedAddress
type StatesTXT map[string]*StateTXT
type StatesMX map[string]*StateMX
type StatesZoneAuth map[string]*StateZoneAuth
type StateBase struct {
err error
}
type StateA struct {
StateBase
records []*RecordA
}
type StateHost struct {
StateBase
records []*RecordHost
}
type StatePTR struct {
StateBase
records []*RecordPTR
}
type StateCNAME struct {
StateBase
records []*RecordCNAME
}
type StateAlias struct {
StateBase
records []*RecordAlias
}
type StateFixedAddress struct {
StateBase
records []*FixedAddress
}
type StateTXT struct {
StateBase
records []*RecordTXT
}
type StateMX struct {
StateBase
records []*RecordMX
}
type StateZoneAuth struct {
StateBase
records []*ZoneAuth
}
/*****************************************************************************\
Query Infoblox for each name/data pair provided by the user and store the
results.
nd is the set of name/data pairs provided as input by the user.
sf specifies the fields to be used in the request.
rf specifies the "_return_fields" and determine which fields are returned.
checkN and checkD are flags that can be used to alter the records search.
If both are false, a single query is made with both the name/data values as
specified by the user (i.e., record:RR?nnn=name&ddd=data, assuming both
name and data were specified by the user). If only checkN, a query is made
with only the name value (record:RR?nnn=name). If only checkD, a query is
made with only the data value (record:RR?ddd=data). If checkN and checkD,
both above queries are made, and the results are combined.
F is Fetcher interface that hold the record object being queried.
\*****************************************************************************/
func getStates(F Fetcher, nd, sf, rf []string, checkN, checkD bool) error {
var name, data string
var err error
for _, nameData := range nd {
F.NewState(nameData)
if name, data, err = splitND(nameData); err == nil {
if name == "" && data == "" && (len(sf) == 0) {
err = Error("cannot GET with no name, data or search fields")
} else {
err = getState(F, nameData, sf, rf, checkN, checkD)
}
}
if err != nil {
F.SetError(nameData, err)
}
ShowDebug("getStates(%T): nameData \"%s\"; err: %v", F, nameData, err)
}
return nil
}
// getState is simply a split out from getStates().
func getState(F Fetcher, nameData string, sf, rf []string, checkN, checkD bool) error {
name, data, _ := splitND(nameData)
var err error
ShowDebug("getState(%s); checkN: %v; checkD: %v; sf: %#v", nameData, checkN, checkD, sf)
// If both are false, use whatever input the user provided
if !checkN && !checkD {
return getStateRecords(F, nameData, name, data, sf, rf)
}
if name != "" && checkN {
ShowDebug("Calling getStateRecords: name: %s; checkN: %v", name, checkN)
if err = getStateRecords(F, nameData, name, "", sf, rf); err != nil {
return err
}
}
if data != "" && checkD {
ShowDebug("Calling getStateRecords: data: %s; checkD: %v", data, checkD)
return getStateRecords(F, nameData, "", data, sf, rf)
}
return nil
}
// Fetch records and populate the nameData map.
func getStateRecords(F Fetcher, nameData, name, data string, sf, rf []string) error {
nKey, dKey := F.GetNDKeys()
object := F.GetObjectType()
rawRecords, err := getRecords(object, nKey, dKey, name, data, sf, rf)
ShowDebug("getStateRecords(%s); name: %s; data: %s; sf: %v; err: %v",
nameData, name, data, sf, err)
if err != nil {
F.SetError(nameData, err)
} else {
F.AddRecords(nameData, rawRecords)
}
return nil
}
// Check for and return any errors in the Fetcher object.
func checkStateErrors(F Fetcher, duo, show bool) []error {
var name, data string
var errors []error
var err error
intro := fmt.Sprintf("Get(%s)", F.GetObjectType())
for _, nameData := range F.GetNDPairs() {
if err = F.GetError(nameData); err != nil {
err = Error("%s \"%s\": %v", intro, nameData, err)
} else if name, data, err = splitND(nameData); err != nil {
err = Error("%s \"%s\": failure parsing nameData: %v", intro, nameData, err)
} else if duo && (name == "" || data == "") {
err = Error("%s \"%s\": need both a name and a data value", intro, nameData)
} else if duo && F.GetRecordCount(nameData) > 1 {
err = Error("%s \"%s\": multiple A records found for unique record", intro, nameData)
}
if err != nil {
errors = append(errors, err)
if show {
Warn("%v", err)
}
}
}
return errors
}