Files
nmon/internal/importcsv/apply.go

128 lines
3.5 KiB
Go

package importcsv
import (
"context"
"fmt"
"github.com/v10networks/nmon/internal/store"
)
// Result counts what Apply wrote.
type Result struct {
DevicesCreated int
CredentialsCreated int
PortsUpdated int
// DeviceIDs are the ids of the devices created, in CSV order.
DeviceIDs []int64
}
// Apply writes the plan in one transaction: new credentials, devices with
// addresses, parent links, then port aliases. Any error rolls everything back.
func Apply(ctx context.Context, st store.Store, p *Plan) (Result, error) {
var res Result
err := st.WithTx(ctx, func(tx store.Store) error {
res = Result{}
ids, err := createDevices(ctx, tx, p.Devices, &res)
if err != nil {
return err
}
if err := linkParents(ctx, tx, p.Devices, ids); err != nil {
return err
}
return applyPorts(ctx, tx, p.Ports, &res)
})
if err != nil {
return Result{}, fmt.Errorf("import: %w", err)
}
return res, nil
}
// createDevices returns the new device ids by planned name.
func createDevices(ctx context.Context, tx store.Store, plans []DevicePlan, res *Result) (map[string]int64, error) {
ids := map[string]int64{}
for _, dp := range plans {
d := &store.Device{
Name: dp.Name, Kind: dp.Kind, Site: dp.Site, Role: dp.Role, Enabled: true, SNMPPort: dp.Port,
}
cid, err := credentialFor(ctx, tx, dp, res)
if err != nil {
return nil, fmt.Errorf("row %d: %w", dp.Row, err)
}
if cid != 0 {
d.SNMPCredentialID = &cid
}
if err := tx.CreateDevice(ctx, d); err != nil {
return nil, fmt.Errorf("row %d: create device %q: %w", dp.Row, dp.Name, err)
}
if len(dp.Addresses) > 0 {
if err := tx.SetAddresses(ctx, d.ID, dp.Addresses); err != nil {
return nil, fmt.Errorf("row %d: addresses of %q: %w", dp.Row, dp.Name, err)
}
}
ids[dp.Name] = d.ID
res.DeviceIDs = append(res.DeviceIDs, d.ID)
res.DevicesCreated++
}
return ids, nil
}
func credentialFor(ctx context.Context, tx store.Store, dp DevicePlan, res *Result) (int64, error) {
if dp.NewCredential == nil {
return dp.CredentialID, nil
}
c := *dp.NewCredential
if err := tx.CreateCredential(ctx, &c); err != nil {
return 0, fmt.Errorf("create credential %q: %w", c.Name, err)
}
res.CredentialsCreated++
return c.ID, nil
}
func linkParents(ctx context.Context, tx store.Store, plans []DevicePlan, ids map[string]int64) error {
for _, dp := range plans {
if len(dp.Parents) == 0 {
continue
}
pids := make([]int64, 0, len(dp.Parents))
for _, key := range dp.Parents {
id, ok := ids[key]
if !ok {
d, err := tx.GetDeviceBySlug(ctx, key)
if err != nil {
return fmt.Errorf("row %d: parent %q: %w", dp.Row, key, err)
}
id = d.ID
}
pids = append(pids, id)
}
if err := tx.SetParents(ctx, ids[dp.Name], store.RelNetwork, pids); err != nil {
return fmt.Errorf("row %d: parents of %q: %w", dp.Row, dp.Name, err)
}
}
return nil
}
func applyPorts(ctx context.Context, tx store.Store, ports []PortPlan, res *Result) error {
byDev := map[int64][]store.Interface{}
for _, pp := range ports {
if _, ok := byDev[pp.DeviceID]; !ok {
ifs, err := tx.ListInterfaces(ctx, pp.DeviceID, false)
if err != nil {
return fmt.Errorf("row %d: %w", pp.Row, err)
}
byDev[pp.DeviceID] = ifs
}
ifs := byDev[pp.DeviceID]
for i := range ifs {
if ifs[i].ID == pp.IfaceID {
ifs[i].Description = pp.Description
if err := tx.UpsertInterfaces(ctx, pp.DeviceID, []store.Interface{ifs[i]}); err != nil {
return fmt.Errorf("row %d: set description on %s: %w", pp.Row, pp.IfName, err)
}
res.PortsUpdated++
}
}
}
return nil
}