-
Notifications
You must be signed in to change notification settings - Fork 0
/
addresses.go
216 lines (193 loc) · 6.5 KB
/
addresses.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
package infoblox
import (
"errors"
"fmt"
"net/netip"
ibclient "github.com/infobloxopen/infoblox-go-client/v2"
"k8s.io/utils/ptr"
)
// Known limitations:
// - Hostname must be a FQDN. We enable DNS for the host record, so Infoblox will return an error if the hostname is not a FQDN.
// - For some reason Infoblox does not assign network view to the host record. Workarounds were provided in the code with tag: [Issue].
// getOrNewHostRecord returns the host record with the given hostname in the given view, or creates a new host record if no host record with the given hostname exists.
func (c *client) getOrNewHostRecord(view, hostname, zone string) (*ibclient.HostRecord, error) {
// [Issue] For some reason Infoblox does not assign view to the host record. Empty netview and dnsview is a workaround to find host.
hostRecord, err := c.objMgr.GetHostRecord("", "", hostname, "", "")
if err != nil {
var notFoundError *ibclient.NotFoundError
if !errors.As(err, ¬FoundError) {
return nil, err
}
}
if hostRecord == nil {
hostRecord = ibclient.NewEmptyHostRecord()
hostRecord.Name = &hostname
hostRecord.NetworkView = view
hostRecord.Ipv4Addrs = []ibclient.HostRecordIpv4Addr{}
hostRecord.Ipv6Addrs = []ibclient.HostRecordIpv6Addr{}
hostRecord.EnableDns = ptr.To(false)
if zone != "" {
hostRecord.EnableDns = ptr.To(true)
hostRecord.View = toDNSView(view)
}
}
return hostRecord, nil
}
// createOrUpdateHostRecord creates or updates a host record and then fetches the updated record.
func (c *client) createOrUpdateHostRecord(hr *ibclient.HostRecord) error {
ref := ""
var err error
if hr.Ref == "" {
ref, err = c.connector.CreateObject(hr)
} else {
prepareHostRecordForUpdate(hr)
ref, err = c.connector.UpdateObject(hr, hr.Ref)
}
if err != nil {
return err
}
return c.connector.GetObject(hr, ref, ibclient.NewQueryParams(false, nil), hr)
}
// getHostRecordAddrInSubnet returns the first IP address in a host record that is in the given subnet.
func getHostRecordAddrInSubnet(hr *ibclient.HostRecord, subnet netip.Prefix) (netip.Addr, bool) {
if subnet.Addr().Is4() {
for _, ip := range hr.Ipv4Addrs {
if ip.Ipv4Addr != nil {
nip, err := netip.ParseAddr(*ip.Ipv4Addr)
if err != nil {
// As a working IPAM system, Infoblox should only return valid IP addresses. But just in case it doesn't, we just skip the address.
continue
}
if subnet.Contains(nip) {
return nip, true
}
}
}
} else {
for _, ip := range hr.Ipv6Addrs {
if ip.Ipv6Addr != nil {
nip, err := netip.ParseAddr(*ip.Ipv6Addr)
if err != nil {
// As a working IPAM system, Infoblox should only return valid IP addresses. But just in case it doesn't, we just skip the address.
continue
}
if subnet.Contains(nip) {
return nip, true
}
}
}
}
return netip.Addr{}, false
}
// GetOrAllocateAddress returns the IP address of the given hostname in the given subnet. If the hostname does not have an IP address in the subnet, it will allocate one.
func (c *client) GetOrAllocateAddress(view string, subnet netip.Prefix, hostname, zone string) (netip.Addr, error) {
hr, err := c.getOrNewHostRecord(view, hostname, zone)
if err != nil {
return netip.Addr{}, fmt.Errorf("failed to get or create Infoblox host record: %w", err)
}
addr, ok := getHostRecordAddrInSubnet(hr, subnet)
if ok {
return addr, nil
}
if subnet.Addr().Is4() {
ipr := ibclient.NewHostRecordIpv4Addr(nextAvailableIBFunc(subnet, view), "", false, "")
hr.Ipv4Addrs = append(hr.Ipv4Addrs, *ipr)
} else {
ipr := ibclient.NewHostRecordIpv6Addr(nextAvailableIBFunc(subnet, view), "", false, "")
hr.Ipv6Addrs = append(hr.Ipv6Addrs, *ipr)
}
// [Issue] this is to reassign netview and view as Infoblox is dropping them for me. Without that updating host record by reference will not work
hr.NetworkView = view
hr.View = toDNSView(view)
if err := c.createOrUpdateHostRecord(hr); err != nil {
return netip.Addr{}, fmt.Errorf("failed to create or update Infoblox host record: %w", err)
}
addr, ok = getHostRecordAddrInSubnet(hr, subnet)
if ok {
return addr, nil
}
return netip.Addr{}, errors.New("failed to allocate IP address: Infoblox host record does not contain a matching IP address")
}
func nextAvailableIBFunc(subnet netip.Prefix, view string) string {
return fmt.Sprintf("func:nextavailableip:%s,%s", subnet.String(), view)
}
// ReleaseAddress releases the IP address of the given hostname in the given subnet.
func (c *client) ReleaseAddress(view string, subnet netip.Prefix, hostname string) error {
hr, err := c.objMgr.GetHostRecord("", "", hostname, "", "")
if err != nil {
return err
}
removed := false
if subnet.Addr().Is4() {
for i, ip := range hr.Ipv4Addrs {
if ip.Ipv4Addr != nil {
nip, err := netip.ParseAddr(*ip.Ipv4Addr)
if err != nil {
continue
}
if subnet.Contains(nip) {
hr.Ipv4Addrs = append(hr.Ipv4Addrs[:i], hr.Ipv4Addrs[i+1:]...)
removed = true
break
}
}
}
} else {
for i, ip := range hr.Ipv6Addrs {
if ip.Ipv6Addr != nil {
nip, err := netip.ParseAddr(*ip.Ipv6Addr)
if err != nil {
continue
}
if subnet.Contains(nip) {
hr.Ipv6Addrs = append(hr.Ipv6Addrs[:i], hr.Ipv6Addrs[i+1:]...)
removed = true
break
}
}
}
}
if !removed {
// The address is not in the host record, so we don't need to do anything.
return nil
}
// [Issue] this is to reassign netview and view as Infoblox is dropping them for me. Without that updating host record by reference will not work
hr.NetworkView = view
hr.View = toDNSView(view)
if len(hr.Ipv4Addrs) == 0 && len(hr.Ipv6Addrs) == 0 {
_, err := c.connector.DeleteObject(hr.Ref)
return err
}
prepareHostRecordForUpdate(hr)
_, err = c.connector.UpdateObject(hr, hr.Ref)
return err
}
func toDNSView(view string) *string {
if view == "" {
return nil
}
if view == "default" {
return &view
}
s := "default." + view
return &s
}
func prepareHostRecordForUpdate(hr *ibclient.HostRecord) {
// We clear zone and network view because Infoblox will return an error if we try to "update" them.
hr.Zone = ""
hr.NetworkView = ""
// ipv4addrs and ipv6addrs are nil after fetching the host record, but the api requires them to be empty arrays.
if hr.Ipv4Addrs == nil {
hr.Ipv4Addrs = []ibclient.HostRecordIpv4Addr{}
}
if hr.Ipv6Addrs == nil {
hr.Ipv6Addrs = []ibclient.HostRecordIpv6Addr{}
}
// clear Host field for all addresses
for i := range hr.Ipv4Addrs {
hr.Ipv4Addrs[i].Host = ""
}
for i := range hr.Ipv6Addrs {
hr.Ipv6Addrs[i].Host = ""
}
}