1 package utils
2
3 import (
4 "database/sql"
5 "database/sql/driver"
6 "errors"
7 "fmt"
8 "net/netip"
9 "strings"
10
11 "edge-infra.dev/pkg/edge/api/graph/model"
12 "edge-infra.dev/pkg/lib/networkvalidator"
13 "edge-infra.dev/pkg/sds/lib/set"
14 )
15
16 type NullInet struct {
17 Inet model.InetType
18 Valid bool
19 }
20
21
22 func (ni *NullInet) Scan(value any) error {
23 if value == nil {
24 ni.Valid = false
25 return nil
26 }
27 ni.Valid = true
28 return ni.Inet.UnmarshalGQL(value)
29 }
30
31
32 func (ni NullInet) Value() (driver.Value, error) {
33 if !ni.Valid {
34 return nil, nil
35 }
36 return ni.Inet, nil
37 }
38
39 type NullIface struct {
40 TerminalInterfaceID sql.NullString
41 MacAddress sql.NullString
42 Dhcp4 sql.NullBool
43 Dhcp6 sql.NullBool
44 Gateway4 sql.NullString
45 Gateway6 sql.NullString
46 TerminalID sql.NullString
47 }
48 type NullAddress struct {
49 TerminalAddressID sql.NullString
50 IP sql.NullString
51 PrefixLen sql.NullInt16
52 Family NullInet
53 TerminalInterfaceID sql.NullString
54 }
55
56 func CreateTerminalModel(terminalID string, clusterEdgeID string, lane *string, terminalRole model.TerminalRoleType, terminalClass *model.TerminalClassType, terminalDiscoverDisks *model.TerminalDiscoverDisksType, terminalBootDisk *string, clusterName string, hostname string, existingEfiPart *string, swapEnabled bool) model.Terminal {
57 discoverDisks := model.TerminalDiscoverDisksTypeEmpty
58 if terminalDiscoverDisks == nil {
59 terminalDiscoverDisks = &discoverDisks
60 }
61 return model.Terminal{
62 TerminalID: terminalID,
63 Lane: lane,
64 Role: terminalRole,
65 Class: terminalClass,
66 DiscoverDisks: terminalDiscoverDisks,
67 BootDisk: terminalBootDisk,
68 ClusterEdgeID: clusterEdgeID,
69 ClusterName: clusterName,
70 Hostname: hostname,
71 ExistingEfiPart: existingEfiPart,
72 SwapEnabled: swapEnabled,
73 }
74 }
75
76 func CreateTerminalIfaceModel(terminalIfaceID string, macAddress string, dhcp4 bool, dhcp6 bool, gateway4 *string, gateway6 *string, terminalID string) model.TerminalInterface {
77 return model.TerminalInterface{
78 TerminalInterfaceID: terminalIfaceID,
79 MacAddress: macAddress,
80 Dhcp4: dhcp4,
81 Dhcp6: dhcp6,
82 Gateway4: gateway4,
83 Gateway6: gateway6,
84 TerminalID: terminalID,
85 }
86 }
87
88 func CreateTerminalAddressModel(terminalAddressID string, ip *string, prefixLen int, family model.InetType, terminalIfaceID string) model.TerminalAddress {
89 return model.TerminalAddress{
90 TerminalAddressID: terminalAddressID,
91 IP: ip,
92 PrefixLen: prefixLen,
93 Family: family,
94 TerminalInterfaceID: terminalIfaceID,
95 }
96 }
97
98 func CreateTerminalDiskModel(terminalDiskID string, terminalID string, includeDisk bool, expectEmpty bool, devicePath string, usePart bool) model.TerminalDisk {
99 return model.TerminalDisk{
100 TerminalDiskID: terminalDiskID,
101 TerminalID: terminalID,
102 IncludeDisk: includeDisk,
103 ExpectEmpty: expectEmpty,
104 DevicePath: devicePath,
105 UsePart: usePart,
106 }
107 }
108
109 func ValidateTerminalCreateInput(terminalInput *model.TerminalCreateInput) error {
110
111
112
113
114 for _, interfaceInput := range terminalInput.Interfaces {
115 if err := ValidateTerminalInterfaceCreateInput(interfaceInput); err != nil {
116 return err
117 }
118 }
119 return nil
120 }
121
122 func ValidateTerminalUpdateInput(terminalInput *model.TerminalUpdateInput) error {
123
124
125
126
127 for _, interfaceInput := range terminalInput.Interfaces {
128 if err := ValidateTerminalInterfaceUpdateInput(interfaceInput.TerminalInterfaceValues); err != nil {
129 return err
130 }
131 }
132 return nil
133 }
134
135
136
137 func ValidateTerminal(terminal *model.Terminal) error {
138 if !terminal.Role.IsValid() {
139 return fmt.Errorf("invalid terminal role: %s", terminal.Role.String())
140 }
141
142 if terminal.Class != nil {
143 if !terminal.Class.IsValid() {
144 return fmt.Errorf("invalid terminal class: %s", terminal.Class.String())
145 }
146 }
147
148 if terminal.DiscoverDisks != nil {
149 if !terminal.DiscoverDisks.IsValid() {
150 return fmt.Errorf("invalid discoverDisks value: %s", terminal.DiscoverDisks.String())
151 }
152 }
153
154 return nil
155 }
156
157 func ValidateTerminalInterfaceCreateInput(interfaceInput *model.TerminalInterfaceCreateInput) error {
158 if interfaceInput.Gateway4 != nil && !networkvalidator.ValidateIP(*interfaceInput.Gateway4) {
159 return fmt.Errorf("invalid gateway4 address: %s", *interfaceInput.Gateway4)
160 }
161 if interfaceInput.Gateway6 != nil && !networkvalidator.ValidateIP(*interfaceInput.Gateway6) {
162 return fmt.Errorf("invalid gateway6 address: %s", *interfaceInput.Gateway6)
163 }
164
165 formattedMac, err := FormatMacAddress(interfaceInput.MacAddress)
166 if err != nil {
167 return err
168 }
169 interfaceInput.MacAddress = formattedMac
170
171
172
173 return nil
174 }
175
176 func ValidateTerminalInterfaceUpdateInput(interfaceInput *model.TerminalInterfaceUpdateInput) error {
177 if interfaceInput.Gateway4 != nil && !networkvalidator.ValidateIP(*interfaceInput.Gateway4) {
178 return fmt.Errorf("invalid gateway4 address: %s", *interfaceInput.Gateway4)
179 }
180 if interfaceInput.Gateway6 != nil && !networkvalidator.ValidateIP(*interfaceInput.Gateway6) {
181 return fmt.Errorf("invalid gateway6 address: %s", *interfaceInput.Gateway6)
182 }
183
184 if interfaceInput.MacAddress != nil {
185 formattedMac, err := FormatMacAddress(*interfaceInput.MacAddress)
186 if err != nil {
187 return err
188 }
189 interfaceInput.MacAddress = &formattedMac
190 }
191
192
193
194 return nil
195 }
196
197
198
199 func ValidateTerminalIface(iface *model.TerminalInterface) error {
200 if iface.Gateway4 != nil && !networkvalidator.ValidateIP(*iface.Gateway4) {
201 return fmt.Errorf("invalid gateway4 address: %s", *iface.Gateway4)
202 }
203
204 formattedMac, err := FormatMacAddress(iface.MacAddress)
205 if err != nil {
206 return err
207 }
208 iface.MacAddress = formattedMac
209 return nil
210 }
211
212 func TerminalAddressesContainsIPv4(addrs []*model.TerminalAddress) (bool, error) {
213 for _, addr := range addrs {
214 if addr.Family == model.InetTypeInet && addr.IP != nil {
215 ipAddr, err := netip.ParseAddr(*addr.IP)
216 if err != nil {
217 return false, err
218 }
219 if ipAddr.Is4() {
220
221 return true, nil
222 }
223 }
224 }
225 return false, nil
226 }
227
228
229 func ValidateTerminalAddress(address *model.TerminalAddress) error {
230 if address.IP != nil {
231 if !networkvalidator.ValidateIP(*address.IP) {
232 return fmt.Errorf("invalid address ip: %s", *address.IP)
233 }
234 if !networkvalidator.ValidateCIDR(*address.IP, address.PrefixLen) {
235 return fmt.Errorf("invalid prefix: %d", address.PrefixLen)
236 }
237 }
238 return nil
239 }
240
241
242 func ValidateAllTerminalAddresses(dhcp4 bool, addresses []*model.TerminalAddress) error {
243 if !dhcp4 {
244 containsIPv4, err := TerminalAddressesContainsIPv4(addresses)
245 if err != nil {
246 return err
247 }
248 if !(containsIPv4 || dhcp4) {
249 return errors.New("terminal address validation failed - missing ipv4 address")
250 }
251 }
252 return nil
253 }
254
255
256 func UpdateTerminal(currentTerminal *model.Terminal, updateTerminal *model.TerminalIDInput) *model.Terminal {
257 currentTerminal.Lane = AssignNotNil(currentTerminal.Lane, updateTerminal.TerminalValues.Lane)
258 currentTerminal.Role = *AssignNotNil(¤tTerminal.Role, updateTerminal.TerminalValues.Role)
259 currentTerminal.Class = AssignNotNil(currentTerminal.Class, updateTerminal.TerminalValues.Class)
260 currentTerminal.DiscoverDisks = AssignNotNil(currentTerminal.DiscoverDisks, updateTerminal.TerminalValues.DiscoverDisks)
261 currentTerminal.BootDisk = AssignNotNil(currentTerminal.BootDisk, updateTerminal.TerminalValues.BootDisk)
262 currentTerminal.PrimaryInterface = AssignNotNil(currentTerminal.PrimaryInterface, updateTerminal.TerminalValues.PrimaryInterface)
263 currentTerminal.ExistingEfiPart = AssignNotNil(currentTerminal.ExistingEfiPart, updateTerminal.TerminalValues.ExistingEfiPart)
264 currentTerminal.SwapEnabled = *AssignNotNil(¤tTerminal.SwapEnabled, updateTerminal.TerminalValues.SwapEnabled)
265 return currentTerminal
266 }
267
268
269 func MergeTerminalInterface(currentIface *model.TerminalInterface, newIface *model.TerminalInterfaceUpdateInput) *model.TerminalInterface {
270 currentIface.Dhcp4 = *AssignNotNil(¤tIface.Dhcp4, newIface.Dhcp4)
271 currentIface.Dhcp6 = *AssignNotNil(¤tIface.Dhcp6, newIface.Dhcp6)
272 currentIface.Gateway4 = AssignNotNil(currentIface.Gateway4, newIface.Gateway4)
273 currentIface.Gateway6 = AssignNotNil(currentIface.Gateway6, newIface.Gateway6)
274 currentIface.MacAddress = *AssignNotNil(¤tIface.MacAddress, newIface.MacAddress)
275 return currentIface
276 }
277
278
279
280 func UpdateTerminalInterface(currentTerminalInterfaces []*model.TerminalInterface, updateInterface *model.TerminalInterfaceIDInput) (*model.TerminalInterface, error) {
281 for _, currentInterface := range currentTerminalInterfaces {
282 if currentInterface.TerminalInterfaceID == updateInterface.TerminalInterfaceID {
283 return MergeTerminalInterface(currentInterface, updateInterface.TerminalInterfaceValues), nil
284 }
285 }
286 return nil, errors.New("cannot update a terminal interface that does not exist")
287 }
288
289 func MergeTerminalAddress(currentAddress *model.TerminalAddress, newAddress *model.TerminalAddressUpdateInput) *model.TerminalAddress {
290 currentAddress.IP = AssignNotNil(currentAddress.IP, newAddress.IP)
291 currentAddress.Family = *AssignNotNil(¤tAddress.Family, newAddress.Family)
292 currentAddress.PrefixLen = *AssignNotNil(¤tAddress.PrefixLen, newAddress.PrefixLen)
293 return currentAddress
294 }
295
296
297 func UpdateTerminalAddress(currentTerminalAddresses []*model.TerminalAddress, updateAddress *model.TerminalAddressIDInput) (*model.TerminalAddress, error) {
298 for _, currentAddress := range currentTerminalAddresses {
299 if currentAddress.TerminalAddressID == updateAddress.TerminalAddressID {
300 return MergeTerminalAddress(currentAddress, updateAddress.TerminalAddressValues), nil
301 }
302 }
303 return nil, errors.New("cannot update a terminal address that does not already exist")
304 }
305
306 func CreateIENodeHostname(macAddress string) string {
307 normalisedMac := strings.ReplaceAll(macAddress, ":", "")
308 return fmt.Sprintf("ien-%s", normalisedMac)
309 }
310
311
312
313
314 func ConvertNullIface(iface NullIface) (*model.TerminalInterface, error) {
315 return &model.TerminalInterface{
316 TerminalInterfaceID: iface.TerminalInterfaceID.String,
317 TerminalID: iface.TerminalID.String,
318 MacAddress: iface.MacAddress.String,
319 Dhcp4: iface.Dhcp4.Bool,
320 Dhcp6: iface.Dhcp6.Bool,
321 Gateway4: &iface.Gateway4.String,
322 Gateway6: &iface.Gateway6.String,
323 }, nil
324 }
325
326
327
328
329 func ConvertNullAddr(addr NullAddress) (*model.TerminalAddress, error) {
330 return &model.TerminalAddress{
331 TerminalAddressID: addr.TerminalAddressID.String,
332 TerminalInterfaceID: addr.TerminalInterfaceID.String,
333 IP: &addr.IP.String,
334 PrefixLen: int(addr.PrefixLen.Int16),
335 Family: addr.Family.Inet,
336 }, nil
337 }
338
339
340
341
342
343 func MatchTerminalMacAddresses(queryMacs []string, terminal *model.Terminal) (bool, error) {
344 if queryMacs == nil || terminal == nil {
345 return false, nil
346 }
347
348 queryMacsSet := set.FromSlice(queryMacs)
349 terminalMacsSet := set.Set[string]{}
350 for _, iface := range terminal.Interfaces {
351 convertedMac, err := FormatMacAddress(iface.MacAddress)
352 if err != nil {
353 return false, err
354 }
355 terminalMacsSet.Add(convertedMac)
356 }
357
358 return len(terminalMacsSet.Intersection(queryMacsSet)) != 0, nil
359 }
360
361 func FormatMacAddress(mac string) (string, error) {
362 return networkvalidator.ValidateMacAddress(mac)
363 }
364
365
366
367 func FormatMacAddresses(macs []string) ([]string, error) {
368 for i, mac := range macs {
369 convertedMac, err := FormatMacAddress(mac)
370 if err != nil {
371 return nil, err
372 }
373 macs[i] = convertedMac
374 }
375 return macs, nil
376 }
377
378 func UpdateTerminalDisk(currentDisk *model.TerminalDisk, updateDisk *model.TerminalDiskUpdateInput) (*model.TerminalDisk, error) {
379 currentDisk.DevicePath = *AssignNotNil(¤tDisk.DevicePath, updateDisk.DevicePath)
380 currentDisk.ExpectEmpty = *AssignNotNil(¤tDisk.ExpectEmpty, updateDisk.ExpectEmpty)
381 currentDisk.IncludeDisk = *AssignNotNil(¤tDisk.IncludeDisk, updateDisk.IncludeDisk)
382 currentDisk.UsePart = *AssignNotNil(¤tDisk.UsePart, updateDisk.UsePart)
383
384 return currentDisk, nil
385 }
386
View as plain text