diff --git a/.golangci.yml b/.golangci.yml index b661abd9..9d1aa2e0 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,7 +13,7 @@ issues: linters: enable: - revive - # - gocyclo # TODO: enable in future + - gocyclo # - dupl # TODO: enable in future # - errorlint # TODO: enable in future # - gocognit # TODO: enable in future diff --git a/README.md b/README.md index 5a3f8a8e..9496bf53 100644 --- a/README.md +++ b/README.md @@ -48,24 +48,24 @@ Example configuration can be found [here](#example-config). ### Source -| Parameter | Description | Source Type | Type | Possible values | Default | Required | -| ------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------- | -------- | --------------------- | ---------- | -------- | -| `source.name` | Name of the data source. | all | str | any | "" | Yes | -| `source.type` | Data source type | all | str | [ovirt, vmware, dnac] | "" | Yes | -| `source.hostname` | Hostname of the data source | all | str | any | "" | Yes | -| `source.port` | Port of the data source | all | int | 0-65536 | 443 | No | -| `source.username` | Username of the data source account. | all | str | any | "" | Yes | -| `source.password` | Password of the data source account. | all | str | any | "" | Yes | -| `source.validateCert` | Enforce TLS certificate validation. | all | bool | [true, false] | false | No | -| `source.tagColor` | TagColor for the source tag. | all | string | any | Predefined | No | -| `source.hostSiteRelations` | Regex relations in format `regex = siteName`, that map each host that satisfies regex to site. | [vmware, ovirt] | []string | any | [] | No | -| `source.clusterSiteRelations` | Regex relations in format `regex = siteName`, that map each cluster that satisfies regex to site. | [vmware, ovirt] | []string | any | [] | No | -| `source.clusterTenantRelations` | Regex relations in format `regex = tenantName`, that map each cluster that satisfies regex to tenant. | [vmware, ovirt] | []string | any | [] | No | -| `source.hostTenantRelations` | Regex relations in format `regex = tenantName`, that map each host that satisfies regex to tenant. | [vmware, ovirt] | []string | any | [] | No | -| `source.vmTenantRelations` | Regex relations in format `regex = tenantName`, that map each vm that satisfies regex to tenant. | [vmware, ovirt] | []string | any | [] | No | -| `source.vlanGroupRelations` | Regex relations in format `regex = vlanGroup`, that map each vlan that satisfies regex to vlanGroup. | all | []string | any | [] | No | -| `source.vlanTenantRelations` | Regex relations in format `regex = tenantName`, that map each vlan that satisfies regex to tenant. | [vmware, ovirt] | []string | any | [] | No | -| `source.customFieldMappings` | Mappings of format `customFieldName = option`. Currently, supported options are `contact`, `owner`, `description`. | [vmware ] | []string | any | [] | No | +| Parameter | Description | Source Type | Type | Possible values | Default | Required | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------ | --------------------- | -------- | --------------------- | ---------- | -------- | +| `source.name` | Name of the data source. | all | str | any | "" | Yes | +| `source.type` | Data source type | all | str | [ovirt, vmware, dnac] | "" | Yes | +| `source.hostname` | Hostname of the data source | all | str | any | "" | Yes | +| `source.port` | Port of the data source | all | int | 0-65536 | 443 | No | +| `source.username` | Username of the data source account. | all | str | any | "" | Yes | +| `source.password` | Password of the data source account. | all | str | any | "" | Yes | +| `source.validateCert` | Enforce TLS certificate validation. | all | bool | [true, false] | false | No | +| `source.tagColor` | TagColor for the source tag. | all | string | any | Predefined | No | +| `source.hostSiteRelations` | Regex relations in format `regex = siteName`, that map each host that satisfies regex to site. | [vmware, ovirt] | []string | any | [] | No | +| `source.clusterSiteRelations` | Regex relations in format `regex = siteName`, that map each cluster that satisfies regex to site. | [vmware, ovirt] | []string | any | [] | No | +| `source.clusterTenantRelations` | Regex relations in format `regex = tenantName`, that map each cluster that satisfies regex to tenant. | [vmware, ovirt] | []string | any | [] | No | +| `source.hostTenantRelations` | Regex relations in format `regex = tenantName`, that map each host that satisfies regex to tenant. | [vmware, ovirt, dnac] | []string | any | [] | No | +| `source.vmTenantRelations` | Regex relations in format `regex = tenantName`, that map each vm that satisfies regex to tenant. | [vmware, ovirt] | []string | any | [] | No | +| `source.vlanGroupRelations` | Regex relations in format `regex = vlanGroup`, that map each vlan that satisfies regex to vlanGroup. | all | []string | any | [] | No | +| `source.vlanTenantRelations` | Regex relations in format `regex = tenantName`, that map each vlan that satisfies regex to tenant. | [vmware, ovirt, dnac] | []string | any | [] | No | +| `source.customFieldMappings` | Mappings of format `customFieldName = option`. Currently, supported options are `contact`, `owner`, `description`. | [vmware ] | []string | any | [] | No | ### Example config @@ -98,12 +98,34 @@ source: hostname: vcenter.example.com username: user password: "top_secret" + clusterSiteRelations: # regex (https://pkg.go.dev/regexp/syntax) cluster name to Site name + - .* = ExampleSite + hostSiteRelations: # regex (https://pkg.go.dev/regexp/syntax) host name to Site name + - .*_NYC = New York + - nyc.* = New York + customFieldMappings: # Here we define map of our custom field names, to 3 option [email, owner, description] + - Mail = email + - Creator = owner + - Description = description + + - name: testvmare + type: vmware + hostname: vcenter-test.example.com + username: user + password: passw0rd + customFieldMappings: # Here we define map of our custom field names, to 3 option [email, owner, description] + - Email = email + - Maintainer = owner + - Notes = description + - name: dnacenter type: dnac hostname: dnac.example.com username: user password: "pa$$w0rd" + vlanTenantRelations: # regex Vlan name to Tenant name + - .* = MyTenant ``` diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 20d093d4..a78cb3da 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -204,47 +204,55 @@ func validateSourceConfig(config *Config) error { default: return fmt.Errorf("%s.type is not valid", externalSourceStr) } - if len(externalSource.HostSiteRelations) > 0 { - err := utils.ValidateRegexRelations(externalSource.HostSiteRelations) - if err != nil { - return fmt.Errorf("%s.hostSiteRelations: %s", externalSourceStr, err) - } + err := validateSourceConfigRelations(externalSource, externalSourceStr) + if err != nil { + return err } - if len(externalSource.ClusterSiteRelations) > 0 { - err := utils.ValidateRegexRelations(externalSource.ClusterSiteRelations) - if err != nil { - return fmt.Errorf("%s.clusterSiteRelations: %s", externalSourceStr, err) - } + } + return nil +} + +func validateSourceConfigRelations(externalSource *SourceConfig, externalSourceStr string) error { + if len(externalSource.HostSiteRelations) > 0 { + err := utils.ValidateRegexRelations(externalSource.HostSiteRelations) + if err != nil { + return fmt.Errorf("%s.hostSiteRelations: %s", externalSourceStr, err) } - if len(externalSource.ClusterTenantRelations) > 0 { - err := utils.ValidateRegexRelations(externalSource.ClusterTenantRelations) - if err != nil { - return fmt.Errorf("%s.clusterTenantRelations: %s", externalSourceStr, err) - } + } + if len(externalSource.ClusterSiteRelations) > 0 { + err := utils.ValidateRegexRelations(externalSource.ClusterSiteRelations) + if err != nil { + return fmt.Errorf("%s.clusterSiteRelations: %s", externalSourceStr, err) } - if len(externalSource.HostTenantRelations) > 0 { - err := utils.ValidateRegexRelations(externalSource.HostTenantRelations) - if err != nil { - return fmt.Errorf("%s.hostTenantRelations: %s", externalSourceStr, err) - } + } + if len(externalSource.ClusterTenantRelations) > 0 { + err := utils.ValidateRegexRelations(externalSource.ClusterTenantRelations) + if err != nil { + return fmt.Errorf("%s.clusterTenantRelations: %s", externalSourceStr, err) } - if len(externalSource.VMTenantRelations) > 0 { - err := utils.ValidateRegexRelations(externalSource.VMTenantRelations) - if err != nil { - return fmt.Errorf("%s.vmTenantRelations: %s", externalSourceStr, err) - } + } + if len(externalSource.HostTenantRelations) > 0 { + err := utils.ValidateRegexRelations(externalSource.HostTenantRelations) + if err != nil { + return fmt.Errorf("%s.hostTenantRelations: %s", externalSourceStr, err) } - if len(externalSource.VlanGroupRelations) > 0 { - err := utils.ValidateRegexRelations(externalSource.VlanGroupRelations) - if err != nil { - return fmt.Errorf("%s.vlanGroupRelations: %v", externalSourceStr, err) - } + } + if len(externalSource.VMTenantRelations) > 0 { + err := utils.ValidateRegexRelations(externalSource.VMTenantRelations) + if err != nil { + return fmt.Errorf("%s.vmTenantRelations: %s", externalSourceStr, err) } - if len(externalSource.VlanTenantRelations) > 0 { - err := utils.ValidateRegexRelations((externalSource.VlanTenantRelations)) - if err != nil { - return fmt.Errorf("%s.vlanTenantRelations: %v", externalSourceStr, err) - } + } + if len(externalSource.VlanGroupRelations) > 0 { + err := utils.ValidateRegexRelations(externalSource.VlanGroupRelations) + if err != nil { + return fmt.Errorf("%s.vlanGroupRelations: %v", externalSourceStr, err) + } + } + if len(externalSource.VlanTenantRelations) > 0 { + err := utils.ValidateRegexRelations((externalSource.VlanTenantRelations)) + if err != nil { + return fmt.Errorf("%s.vlanTenantRelations: %v", externalSourceStr, err) } } return nil diff --git a/internal/parser/parser_valid_test.go b/internal/parser/parser_valid_test.go index 7dac1204..34fe3266 100644 --- a/internal/parser/parser_valid_test.go +++ b/internal/parser/parser_valid_test.go @@ -1,7 +1,6 @@ package parser import ( - "fmt" "path/filepath" "reflect" "testing" @@ -9,129 +8,6 @@ import ( "github.com/bl4ko/netbox-ssot/internal/constants" ) -func findDifferentFields(firstConf Config, secondConf Config) map[string]string { - field2diff := make(map[string]string) - - // Compare logger fields - if firstConf.Logger.Level != secondConf.Logger.Level { - field2diff["Logger.Level"] = fmt.Sprintf("%d != %d", firstConf.Logger.Level, secondConf.Logger.Level) - } - if firstConf.Logger.Dest != secondConf.Logger.Dest { - field2diff["Logger.Dest"] = fmt.Sprintf("%s != %s", firstConf.Logger.Dest, secondConf.Logger.Dest) - } - - // Compare netbox fields - if firstConf.Netbox.APIToken != secondConf.Netbox.APIToken { - field2diff["Netbox.ApiToken"] = fmt.Sprintf("%s != %s", firstConf.Netbox.APIToken, secondConf.Netbox.APIToken) - } - if firstConf.Netbox.Hostname != secondConf.Netbox.Hostname { - field2diff["Netbox.Hostname"] = fmt.Sprintf("%s != %s", firstConf.Netbox.Hostname, secondConf.Netbox.Hostname) - } - if firstConf.Netbox.HTTPScheme != secondConf.Netbox.HTTPScheme { - field2diff["Netbox.HTTPScheme"] = fmt.Sprintf("%s != %s", firstConf.Netbox.HTTPScheme, secondConf.Netbox.HTTPScheme) - } - if firstConf.Netbox.Port != secondConf.Netbox.Port { - field2diff["Netbox.Port"] = fmt.Sprintf("%d != %d", firstConf.Netbox.Port, secondConf.Netbox.Port) - } - if firstConf.Netbox.ValidateCert != secondConf.Netbox.ValidateCert { - field2diff["Netbox.ValidateCert"] = fmt.Sprintf("%t != %t", firstConf.Netbox.ValidateCert, secondConf.Netbox.ValidateCert) - } - if firstConf.Netbox.Timeout != secondConf.Netbox.Timeout { - field2diff["Netbox.Timeout"] = fmt.Sprintf("%d != %d", firstConf.Netbox.Timeout, secondConf.Netbox.Timeout) - } - if firstConf.Netbox.Tag != secondConf.Netbox.Tag { - field2diff["Netbox.Tag"] = fmt.Sprintf("%s != %s", firstConf.Netbox.Tag, secondConf.Netbox.Tag) - } - if firstConf.Netbox.TagColor != secondConf.Netbox.TagColor { - field2diff["Netbox.TagColor"] = fmt.Sprintf("%s != %s", firstConf.Netbox.TagColor, secondConf.Netbox.TagColor) - } - - // Compare sources list - if len(firstConf.Sources) != len(secondConf.Sources) { - field2diff["Sources"] = fmt.Sprintf("len(%d) != len(%d)", len(firstConf.Sources), len(secondConf.Sources)) - } - for i := 0; i < len(firstConf.Sources); i++ { - firstSource := firstConf.Sources[i] - secondSource := secondConf.Sources[i] - if firstSource.Name != secondSource.Name { - field2diff[fmt.Sprintf("Sources[%d].Name", i)] = fmt.Sprintf("%s != %s", firstSource.Name, secondSource.Name) - } - if firstSource.Type != secondSource.Type { - field2diff[fmt.Sprintf("Sources[%d].Type", i)] = fmt.Sprintf("%s != %s", firstSource.Type, secondSource.Type) - } - if firstSource.HTTPScheme != secondSource.HTTPScheme { - field2diff[fmt.Sprintf("Sources[%d].HTTPScheme", i)] = fmt.Sprintf("%s != %s", firstSource.HTTPScheme, secondSource.HTTPScheme) - } - if firstSource.Hostname != secondSource.Hostname { - field2diff[fmt.Sprintf("Sources[%d].Hostname", i)] = fmt.Sprintf("%s != %s", firstSource.Hostname, secondSource.Hostname) - } - if firstSource.Port != secondSource.Port { - field2diff[fmt.Sprintf("Sources[%d].Port", i)] = fmt.Sprintf("%d != %d", firstSource.Port, secondSource.Port) - } - if firstSource.Username != secondSource.Username { - field2diff[fmt.Sprintf("Sources[%d].Username", i)] = fmt.Sprintf("%s != %s", firstSource.Username, secondSource.Username) - } - if firstSource.Password != secondSource.Password { - field2diff[fmt.Sprintf("Sources[%d].Password", i)] = fmt.Sprintf("%s != %s", firstSource.Password, secondSource.Password) - } - if firstSource.Tag != secondSource.Tag { - field2diff[fmt.Sprintf("Sources[%d].Tag", i)] = fmt.Sprintf("%s != %s", firstSource.Tag, secondSource.Tag) - } - if firstSource.TagColor != secondSource.TagColor { - field2diff[fmt.Sprintf("Sources[%d].TagColor", i)] = fmt.Sprintf("%s != %s", firstSource.TagColor, secondSource.TagColor) - } - if len(firstSource.PermittedSubnets) != len(secondSource.PermittedSubnets) { - field2diff[fmt.Sprintf("Sources[%d].PermittedSubnets", i)] = fmt.Sprintf("len(%d) != len(%d)", len(firstSource.PermittedSubnets), len(secondSource.PermittedSubnets)) - } - for j := 0; j < len(firstSource.PermittedSubnets); j++ { - if firstSource.PermittedSubnets[j] != secondSource.PermittedSubnets[j] { - field2diff[fmt.Sprintf("Sources[%d].PermittedSubnets[%d]", i, j)] = fmt.Sprintf("%s != %s", firstSource.PermittedSubnets[j], secondSource.PermittedSubnets[j]) - } - } - if len(firstSource.ClusterSiteRelations) != len(secondSource.ClusterSiteRelations) { - field2diff[fmt.Sprintf("Sources[%d].ClusterSiteRelations", i)] = fmt.Sprintf("len(%d) != len(%d)", len(firstSource.ClusterSiteRelations), len(secondSource.ClusterSiteRelations)) - } - for j := 0; j < len(firstSource.ClusterSiteRelations); j++ { - if firstSource.ClusterSiteRelations[j] != secondSource.ClusterSiteRelations[j] { - field2diff[fmt.Sprintf("Sources[%d].ClusterSiteRelations[%d]", i, j)] = fmt.Sprintf("%s != %s", firstSource.ClusterSiteRelations[j], secondSource.ClusterSiteRelations[j]) - } - } - if len(firstSource.HostSiteRelations) != len(secondSource.HostSiteRelations) { - field2diff[fmt.Sprintf("Sources[%d].HostSiteRelations", i)] = fmt.Sprintf("len(%d) != len(%d)", len(firstSource.HostSiteRelations), len(secondSource.HostSiteRelations)) - } - for j := 0; j < len(firstSource.HostSiteRelations); j++ { - if firstSource.HostSiteRelations[j] != secondSource.HostSiteRelations[j] { - field2diff[fmt.Sprintf("Sources[%d].HostSiteRelations[%d]", i, j)] = fmt.Sprintf("%s != %s", firstSource.HostSiteRelations[j], secondSource.HostSiteRelations[j]) - } - } - if len(firstSource.ClusterTenantRelations) != len(secondSource.ClusterTenantRelations) { - field2diff[fmt.Sprintf("Sources[%d].ClusterTenantRelations", i)] = fmt.Sprintf("len(%d) != len(%d)", len(firstSource.ClusterTenantRelations), len(secondSource.ClusterTenantRelations)) - } - for j := 0; j < len(firstSource.ClusterTenantRelations); j++ { - if firstSource.ClusterTenantRelations[j] != secondSource.ClusterTenantRelations[j] { - field2diff[fmt.Sprintf("Sources[%d].ClusterTenantRelations[%d]", i, j)] = fmt.Sprintf("%s != %s", firstSource.ClusterTenantRelations[j], secondSource.ClusterTenantRelations[j]) - } - } - if len(firstSource.HostTenantRelations) != len(secondSource.HostTenantRelations) { - field2diff[fmt.Sprintf("Sources[%d].HostTenantRelations", i)] = fmt.Sprintf("len(%d) != len(%d)", len(firstSource.HostTenantRelations), len(secondSource.HostTenantRelations)) - } - for j := 0; j < len(firstSource.HostTenantRelations); j++ { - if firstSource.HostTenantRelations[j] != secondSource.HostTenantRelations[j] { - field2diff[fmt.Sprintf("Sources[%d].HostTenantRelations[%d]", i, j)] = fmt.Sprintf("%s != %s", firstSource.HostTenantRelations[j], secondSource.HostTenantRelations[j]) - } - } - if len(firstSource.VMTenantRelations) != len(secondSource.VMTenantRelations) { - field2diff[fmt.Sprintf("Sources[%d].VmTenantRelations", i)] = fmt.Sprintf("len(%d) != len(%d)", len(firstSource.VMTenantRelations), len(secondSource.VMTenantRelations)) - } - for j := 0; j < len(firstSource.VMTenantRelations); j++ { - if firstSource.VMTenantRelations[j] != secondSource.VMTenantRelations[j] { - field2diff[fmt.Sprintf("Sources[%d].VmTenantRelations[%d]", i, j)] = fmt.Sprintf("%s != %s", firstSource.VMTenantRelations[j], secondSource.VMTenantRelations[j]) - } - } - } - return field2diff -} - func TestValidConfig(t *testing.T) { filename := filepath.Join("testdata", "valid_config.yaml") want := &Config{ @@ -211,7 +87,6 @@ func TestValidConfig(t *testing.T) { return } if !reflect.DeepEqual(got, want) { - field2diff := findDifferentFields(*got, *want) - t.Errorf("got = %v\nwant %v\nDifferent fields: %v", got, want, field2diff) + t.Errorf("got = %v\nwant %v\n", got, want) } } diff --git a/internal/source/dnac/dnac.go b/internal/source/dnac/dnac.go index 24d6d1d2..1d1dceb9 100644 --- a/internal/source/dnac/dnac.go +++ b/internal/source/dnac/dnac.go @@ -32,6 +32,7 @@ type Source struct { InterfaceID2nbInterface map[string]*objects.Interface // InterfaceId -> nbInterface // User defined relations + HostTenantRelations map[string]string VlanGroupRelations map[string]string VlanTenantRelations map[string]string } @@ -45,9 +46,11 @@ func (ds *Source) Init() error { // Initialize regex relations for this source ds.VlanGroupRelations = utils.ConvertStringsToRegexPairs(ds.SourceConfig.VlanGroupRelations) - ds.Logger.Debug("VlanGroupRelations: ", ds.VlanGroupRelations) + ds.Logger.Debugf("VlanGroupRelations: %s", ds.VlanGroupRelations) ds.VlanTenantRelations = utils.ConvertStringsToRegexPairs(ds.SourceConfig.VlanTenantRelations) - ds.Logger.Debug("VlanTenantRelations: ", ds.VlanTenantRelations) + ds.Logger.Debugf("VlanTenantRelations: %s", ds.VlanTenantRelations) + ds.HostTenantRelations = utils.ConvertStringsToRegexPairs(ds.SourceConfig.HostTenantRelations) + ds.Logger.Debugf("HostTenantRelations: %s", ds.HostTenantRelations) // Initialize items from vsphere API to local storage initFunctions := []func(*dnac.Client) error{ diff --git a/internal/source/dnac/dnac_init.go b/internal/source/dnac/dnac_init.go index 308caa03..8b6055a0 100644 --- a/internal/source/dnac/dnac_init.go +++ b/internal/source/dnac/dnac_init.go @@ -55,20 +55,14 @@ func (ds *Source) InitDevices(c *dnac.Client) error { ds.Vlans = make(map[int]dnac.ResponseDevicesGetDeviceInterfaceVLANsResponse) for _, device := range allDevices { ds.Devices[device.ID] = device - err := ds.addVlansForDevice(c, device.ID) - if err != nil { - return fmt.Errorf("init vlans for device[%s]: %s", device.ID, err) - } + ds.initVlansForDevice(c, device.ID) } return nil } // Function that gets all vlans for device id. -func (ds *Source) addVlansForDevice(c *dnac.Client, deviceID string) error { - vlans, _, err := c.Devices.GetDeviceInterfaceVLANs(deviceID, nil) - if err != nil { - return err - } +func (ds *Source) initVlansForDevice(c *dnac.Client, deviceID string) { + vlans, _, _ := c.Devices.GetDeviceInterfaceVLANs(deviceID, nil) if vlans != nil { for _, vlan := range *vlans.Response { if vlan.VLANNumber != nil { @@ -76,7 +70,6 @@ func (ds *Source) addVlansForDevice(c *dnac.Client, deviceID string) error { } } } - return nil } func (ds *Source) InitInterfaces(c *dnac.Client) error { diff --git a/internal/source/dnac/dnac_sync.go b/internal/source/dnac/dnac_sync.go index 6350bb70..bb3616eb 100644 --- a/internal/source/dnac/dnac_sync.go +++ b/internal/source/dnac/dnac_sync.go @@ -161,6 +161,11 @@ func (ds *Source) SyncDevices(nbi *inventory.NetboxInventory) error { return fmt.Errorf("add device type: %s", err) } + deviceTenant, err := common.MatchHostToTenant(nbi, device.Hostname, ds.HostTenantRelations) + if err != nil { + return fmt.Errorf("hostTenant: %s", err) + } + nbDevice, err := nbi.AddDevice(&objects.Device{ NetboxObject: objects.NetboxObject{ Tags: ds.Config.SourceTags, @@ -170,6 +175,7 @@ func (ds *Source) SyncDevices(nbi *inventory.NetboxInventory) error { }, }, Name: device.Hostname, + Tenant: deviceTenant, DeviceRole: deviceRole, SerialNumber: device.SerialNumber, Platform: platform, diff --git a/internal/source/ovirt/ovirt.go b/internal/source/ovirt/ovirt.go index 4354b9db..efeae590 100644 --- a/internal/source/ovirt/ovirt.go +++ b/internal/source/ovirt/ovirt.go @@ -12,8 +12,10 @@ import ( ovirtsdk4 "github.com/ovirt/go-ovirt" ) -// Source represents an oVirt source. -type Source struct { +// OVirtSource represents an oVirt source. +// +//nolint:revive +type OVirtSource struct { common.Config Disks map[string]*ovirtsdk4.Disk DataCenters map[string]*ovirtsdk4.DataCenter @@ -37,7 +39,7 @@ type NetworkData struct { } // Function that initializes state from ovirt api to local storage. -func (o *Source) Init() error { +func (o *OVirtSource) Init() error { // Initialize regex relations o.Logger.Debug("Initializing regex relations for oVirt source ", o.SourceConfig.Name) o.HostSiteRelations = utils.ConvertStringsToRegexPairs(o.SourceConfig.HostSiteRelations) @@ -92,7 +94,7 @@ func (o *Source) Init() error { } // Function that syncs all data from oVirt to Netbox. -func (o *Source) Sync(nbi *inventory.NetboxInventory) error { +func (o *OVirtSource) Sync(nbi *inventory.NetboxInventory) error { syncFunctions := []func(*inventory.NetboxInventory) error{ o.syncNetworks, o.syncDatacenters, diff --git a/internal/source/ovirt/ovirt_init.go b/internal/source/ovirt/ovirt_init.go index b92ffde5..70a0471d 100644 --- a/internal/source/ovirt/ovirt_init.go +++ b/internal/source/ovirt/ovirt_init.go @@ -7,7 +7,7 @@ import ( ) // Fetches networks from ovirt api and stores them to local object. -func (o *Source) InitNetworks(conn *ovirtsdk4.Connection) error { +func (o *OVirtSource) InitNetworks(conn *ovirtsdk4.Connection) error { networksResponse, err := conn.SystemService().NetworksService().List().Send() if err != nil { return fmt.Errorf("init oVirt networks: %v", err) @@ -32,7 +32,7 @@ func (o *Source) InitNetworks(conn *ovirtsdk4.Connection) error { return nil } -func (o *Source) InitDisks(conn *ovirtsdk4.Connection) error { +func (o *OVirtSource) InitDisks(conn *ovirtsdk4.Connection) error { // Get the disks disksResponse, err := conn.SystemService().DisksService().List().Send() if err != nil { @@ -50,7 +50,7 @@ func (o *Source) InitDisks(conn *ovirtsdk4.Connection) error { return nil } -func (o *Source) InitDataCenters(conn *ovirtsdk4.Connection) error { +func (o *OVirtSource) InitDataCenters(conn *ovirtsdk4.Connection) error { dataCentersResponse, err := conn.SystemService().DataCentersService().List().Send() if err != nil { return fmt.Errorf("failed to get oVirt data centers: %v", err) @@ -68,7 +68,7 @@ func (o *Source) InitDataCenters(conn *ovirtsdk4.Connection) error { } // Function that queries ovirt api for clusters and stores them locally. -func (o *Source) InitClusters(conn *ovirtsdk4.Connection) error { +func (o *OVirtSource) InitClusters(conn *ovirtsdk4.Connection) error { clustersResponse, err := conn.SystemService().ClustersService().List().Send() if err != nil { return fmt.Errorf("failed to get oVirt clusters: %v", err) @@ -86,7 +86,7 @@ func (o *Source) InitClusters(conn *ovirtsdk4.Connection) error { } // Function that queries ovirt api for hosts and stores them locally. -func (o *Source) InitHosts(conn *ovirtsdk4.Connection) error { +func (o *OVirtSource) InitHosts(conn *ovirtsdk4.Connection) error { hostsResponse, err := conn.SystemService().HostsService().List().Follow("nics").Send() if err != nil { return fmt.Errorf("failed to get oVirt hosts: %+v", err) @@ -104,7 +104,7 @@ func (o *Source) InitHosts(conn *ovirtsdk4.Connection) error { } // Function that queries the ovirt api for vms and stores them locally. -func (o *Source) InitVms(conn *ovirtsdk4.Connection) error { +func (o *OVirtSource) InitVms(conn *ovirtsdk4.Connection) error { vmsResponse, err := conn.SystemService().VmsService().List().Follow("nics,diskattachments,reporteddevices").Send() if err != nil { return fmt.Errorf("failed to get oVirt vms: %+v", err) diff --git a/internal/source/ovirt/ovirt_sync.go b/internal/source/ovirt/ovirt_sync.go index 3a67e8d9..82d09922 100644 --- a/internal/source/ovirt/ovirt_sync.go +++ b/internal/source/ovirt/ovirt_sync.go @@ -13,7 +13,7 @@ import ( ) // Syncs networks received from oVirt API to the netbox. -func (o *Source) syncNetworks(nbi *inventory.NetboxInventory) error { +func (o *OVirtSource) syncNetworks(nbi *inventory.NetboxInventory) error { for _, network := range o.Networks.OVirtNetworks { name, exists := network.Name() if !exists { @@ -57,7 +57,7 @@ func (o *Source) syncNetworks(nbi *inventory.NetboxInventory) error { return nil } -func (o *Source) syncDatacenters(nbi *inventory.NetboxInventory) error { +func (o *OVirtSource) syncDatacenters(nbi *inventory.NetboxInventory) error { // First sync oVirt DataCenters as NetBoxClusterGroups for _, datacenter := range o.DataCenters { name, exists := datacenter.Name() @@ -85,7 +85,7 @@ func (o *Source) syncDatacenters(nbi *inventory.NetboxInventory) error { return nil } -func (o *Source) syncClusters(nbi *inventory.NetboxInventory) error { +func (o *OVirtSource) syncClusters(nbi *inventory.NetboxInventory) error { clusterType := &objects.ClusterType{ NetboxObject: objects.NetboxObject{ Tags: o.Config.SourceTags, @@ -172,7 +172,7 @@ func (o *Source) syncClusters(nbi *inventory.NetboxInventory) error { // Host in oVirt is a represented as device in netbox with a // custom role Server. -func (o *Source) syncHosts(nbi *inventory.NetboxInventory) error { +func (o *OVirtSource) syncHosts(nbi *inventory.NetboxInventory) error { for hostID, host := range o.Hosts { hostName, exists := host.Name() if !exists { @@ -328,7 +328,7 @@ func (o *Source) syncHosts(nbi *inventory.NetboxInventory) error { return nil } -func (o *Source) syncHostNics(nbi *inventory.NetboxInventory, ovirtHost *ovirtsdk4.Host, nbHost *objects.Device) error { +func (o *OVirtSource) syncHostNics(nbi *inventory.NetboxInventory, ovirtHost *ovirtsdk4.Host, nbHost *objects.Device) error { if nics, exists := ovirtHost.Nics(); exists { master2slave := make(map[string][]string) // masterId: [slaveId1, slaveId2, ...] parent2child := make(map[string][]string) // parentId: [childId, ... ] @@ -343,150 +343,10 @@ func (o *Source) syncHostNics(nbi *inventory.NetboxInventory, ovirtHost *ovirtsd hostIP = utils.Lookup(hostAddress) } - var err error - - // First loop through all nics - for _, nic := range nics.Slice() { - nicID, exists := nic.Id() - if !exists { - o.Logger.Warning("id for oVirt nic with id ", nicID, " is empty. This should not happen! Skipping...") - continue - } - nicName, exists := nic.Name() - if !exists { - o.Logger.Warning("name for oVirt nic with id ", nicID, " is empty.") - } - // var nicType *objects.InterfaceType - nicSpeedBips, exists := nic.Speed() - if !exists { - o.Logger.Warning("speed for oVirt nic with id ", nicID, " is empty.") - } - nicSpeedKbps := nicSpeedBips / constants.KB - - nicMtu, exists := nic.Mtu() - if !exists { - o.Logger.Warning("mtu for oVirt nic with id ", nicID, " is empty.") - } - - nicComment, _ := nic.Comment() - - var nicEnabled bool - ovirtNicStatus, exists := nic.Status() - if exists { - switch ovirtNicStatus { - case ovirtsdk4.NICSTATUS_UP: - nicEnabled = true - default: - nicEnabled = false - } - } - - // bridged, exists := nic.Bridged() // TODO: bridged interface - // if exists { - // if bridged { - // // This interface is bridged - // fmt.Printf("nic[%s] is bridged\n", nicName) - // } - // } - - // Determine nic type (virtual, physical, bond...) - var nicType *objects.InterfaceType - nicBaseInterface, exists := nic.BaseInterface() - if exists { - // This interface is a vlan bond. We treat is as a virtual interface - nicType = &objects.VirtualInterfaceType - parent2child[nicBaseInterface] = append(parent2child[nicBaseInterface], nicID) - } - - nicBonding, exists := nic.Bonding() - if exists { - // Bond interface, we give it a type of LAG - nicType = &objects.LAGInterfaceType - slaves, exists := nicBonding.Slaves() - if exists { - for _, slave := range slaves.Slice() { - master2slave[nicID] = append(master2slave[nicID], slave.MustId()) - } - } - } - - if nicType == nil { - // This is a physical interface. - nicType = objects.IfaceSpeed2IfaceType[objects.InterfaceSpeed(nicSpeedKbps)] - if nicType == nil { - nicType = &objects.OtherInterfaceType - } - } - - var nicVlan *objects.Vlan - vlan, exists := nic.Vlan() - if exists { - vlanID, exists := vlan.Id() - if exists { - vlanName := o.Networks.Vid2Name[int(vlanID)] - // Get vlanGroup from relation - vlanGroup, err := common.MatchVlanToGroup(nbi, vlanName, o.VlanGroupRelations) - if err != nil { - return err - } - // Get vlan from inventory - nicVlan = nbi.VlansIndexByVlanGroupIDAndVID[vlanGroup.ID][int(vlanID)] - } - } - - var nicTaggedVlans []*objects.Vlan - if nicVlan != nil { - nicTaggedVlans = []*objects.Vlan{nicVlan} - } - - newInterface := &objects.Interface{ - NetboxObject: objects.NetboxObject{ - Tags: o.Config.SourceTags, - Description: nicComment, - CustomFields: map[string]string{ - constants.CustomFieldSourceName: o.SourceConfig.Name, - constants.CustomFieldSourceIDName: nicID, - }, - }, - Device: nbHost, - Name: nicName, - Speed: objects.InterfaceSpeed(nicSpeedKbps), - Status: nicEnabled, - MTU: int(nicMtu), - Type: nicType, - TaggedVlans: nicTaggedVlans, - } - - // Extract ip info - if nicIPv4, exists := nic.Ip(); exists { - if nicAddress, exists := nicIPv4.Address(); exists { - mask := 32 - if nicMask, exists := nicIPv4.Netmask(); exists { - mask, err = utils.MaskToBits(nicMask) - if err != nil { - return fmt.Errorf("mask to bits: %s", err) - } - } - ipv4Address := fmt.Sprintf("%s/%d", nicAddress, mask) - nicID2IPv4[nicID] = ipv4Address - } - } - if nicIPv6, exists := nic.Ipv6(); exists { - if nicAddress, exists := nicIPv6.Address(); exists { - mask := 128 - if nicMask, exists := nicIPv6.Netmask(); exists { - mask, err = utils.MaskToBits(nicMask) - if err != nil { - return fmt.Errorf("mask to bits: %s", err) - } - } - ipv6Address := fmt.Sprintf("%s/%d", nicAddress, mask) - nicID2IPv6[nicID] = ipv6Address - } - } - - processedNicsIDs[nicID] = true - nicID2nic[nicID] = newInterface + // First loop, we loop through all the nics and collect all the information + err := o.collectHostNicsData(nbHost, nbi, nics, parent2child, master2slave, nicID2nic, processedNicsIDs, nicID2IPv4, nicID2IPv6) + if err != nil { + return fmt.Errorf("collect host nics data: %s", err) } // Second loop to add relations between interfaces (e.g. [eno1, eno2] -> bond1) @@ -598,151 +458,166 @@ func (o *Source) syncHostNics(nbi *inventory.NetboxInventory, ovirtHost *ovirtsd return nil } -func (o *Source) syncVms(nbi *inventory.NetboxInventory) error { - for vmID, vm := range o.Vms { - // VM name, which is used as unique identifier for VMs in Netbox - vmName, exists := vm.Name() +func (o *OVirtSource) collectHostNicsData(nbHost *objects.Device, nbi *inventory.NetboxInventory, nics *ovirtsdk4.HostNicSlice, parent2child map[string][]string, master2slave map[string][]string, nicID2nic map[string]*objects.Interface, processedNicsIDs map[string]bool, nicID2IPv4 map[string]string, nicID2IPv6 map[string]string) error { + for _, nic := range nics.Slice() { + nicID, exists := nic.Id() if !exists { - o.Logger.Warning("name for oVirt vm with id ", vmID, " is empty. VM has to have unique name to be synced to netbox. Skipping...") + o.Logger.Warning("id for oVirt nic with id ", nicID, " is empty. This should not happen! Skipping...") + continue } - - // VM's Cluster - var vmCluster *objects.Cluster - cluster, exists := vm.Cluster() - if exists { - if _, ok := o.Clusters[cluster.MustId()]; ok { - vmCluster = nbi.ClustersIndexByName[o.Clusters[cluster.MustId()].MustName()] - } + nicName, exists := nic.Name() + if !exists { + o.Logger.Warning("name for oVirt nic with id ", nicID, " is empty.") } + // var nicType *objects.InterfaceType + nicSpeedBips, exists := nic.Speed() + if !exists { + o.Logger.Warning("speed for oVirt nic with id ", nicID, " is empty.") + } + nicSpeedKbps := nicSpeedBips / constants.KB - // Get VM's site,tenant and platform from cluster - var vmTenantGroup *objects.TenantGroup - var vmTenant *objects.Tenant - var vmSite *objects.Site - if vmCluster != nil { - vmTenantGroup = vmCluster.TenantGroup - vmTenant = vmCluster.Tenant - vmSite = vmCluster.Site + nicMtu, exists := nic.Mtu() + if !exists { + o.Logger.Warning("mtu for oVirt nic with id ", nicID, " is empty.") } - // VM's Status - var vmStatus *objects.VMStatus - status, exists := vm.Status() + nicComment, _ := nic.Comment() + + var nicEnabled bool + ovirtNicStatus, exists := nic.Status() if exists { - switch status { - case ovirtsdk4.VMSTATUS_UP: - vmStatus = &objects.VMStatusActive + switch ovirtNicStatus { + case ovirtsdk4.NICSTATUS_UP: + nicEnabled = true default: - vmStatus = &objects.VMStatusOffline + nicEnabled = false } } - // VM's Host Device (server) - var vmHostDevice *objects.Device - if host, exists := vm.Host(); exists { - if oHost, ok := o.Hosts[host.MustId()]; ok { - if oHostName, ok := oHost.Name(); ok { - vmHostDevice = nbi.DevicesIndexByNameAndSiteID[oHostName][vmSite.ID] - } - } + // bridged, exists := nic.Bridged() // TODO: bridged interface + // if exists { + // if bridged { + // // This interface is bridged + // fmt.Printf("nic[%s] is bridged\n", nicName) + // } + // } + + // Determine nic type (virtual, physical, bond...) + var nicType *objects.InterfaceType + nicBaseInterface, exists := nic.BaseInterface() + if exists { + // This interface is a vlan bond. We treat is as a virtual interface + nicType = &objects.VirtualInterfaceType + parent2child[nicBaseInterface] = append(parent2child[nicBaseInterface], nicID) } - // vmVCPUs - var vmVCPUs float32 - if cpuData, exists := vm.Cpu(); exists { - if cpuTopology, exists := cpuData.Topology(); exists { - if cores, exists := cpuTopology.Cores(); exists { - vmVCPUs = float32(cores) + nicBonding, exists := nic.Bonding() + if exists { + // Bond interface, we give it a type of LAG + nicType = &objects.LAGInterfaceType + slaves, exists := nicBonding.Slaves() + if exists { + for _, slave := range slaves.Slice() { + master2slave[nicID] = append(master2slave[nicID], slave.MustId()) } } } - // Memory - var vmMemorySizeBytes int64 - if memory, exists := vm.Memory(); exists { - vmMemorySizeBytes = memory + if nicType == nil { + // This is a physical interface. + nicType = objects.IfaceSpeed2IfaceType[objects.InterfaceSpeed(nicSpeedKbps)] + if nicType == nil { + nicType = &objects.OtherInterfaceType + } } - // Disks - var vmDiskSizeBytes int64 - if diskAttachment, exists := vm.DiskAttachments(); exists { - for _, diskAttachment := range diskAttachment.Slice() { - if ovirtDisk, exists := diskAttachment.Disk(); exists { - disk := o.Disks[ovirtDisk.MustId()] - if provisionedDiskSize, exists := disk.ProvisionedSize(); exists { - vmDiskSizeBytes += provisionedDiskSize - } + var nicVlan *objects.Vlan + vlan, exists := nic.Vlan() + if exists { + vlanID, exists := vlan.Id() + if exists { + vlanName := o.Networks.Vid2Name[int(vlanID)] + // Get vlanGroup from relation + vlanGroup, err := common.MatchVlanToGroup(nbi, vlanName, o.VlanGroupRelations) + if err != nil { + return err } + // Get vlan from inventory + nicVlan = nbi.VlansIndexByVlanGroupIDAndVID[vlanGroup.ID][int(vlanID)] } } - // VM's comments - var vmComments string - if comments, exists := vm.Comment(); exists { - vmComments = comments + var nicTaggedVlans []*objects.Vlan + if nicVlan != nil { + nicTaggedVlans = []*objects.Vlan{nicVlan} } - // VM's Platform - var vmPlatform *objects.Platform - vmOsType := "Generic OS" - vmOsVersion := "Generic Version" - if guestOs, exists := vm.GuestOperatingSystem(); exists { - if guestOsType, exists := guestOs.Distribution(); exists { - vmOsType = guestOsType - } - if guestOsKernel, exists := guestOs.Kernel(); exists { - if guestOsVersion, exists := guestOsKernel.Version(); exists { - if osFullVersion, exists := guestOsVersion.FullVersion(); exists { - vmOsVersion = osFullVersion + newInterface := &objects.Interface{ + NetboxObject: objects.NetboxObject{ + Tags: o.Config.SourceTags, + Description: nicComment, + CustomFields: map[string]string{ + constants.CustomFieldSourceName: o.SourceConfig.Name, + constants.CustomFieldSourceIDName: nicID, + }, + }, + Device: nbHost, + Name: nicName, + Speed: objects.InterfaceSpeed(nicSpeedKbps), + Status: nicEnabled, + MTU: int(nicMtu), + Type: nicType, + TaggedVlans: nicTaggedVlans, + } + + var err error + // Extract ip info + if nicIPv4, exists := nic.Ip(); exists { + if nicAddress, exists := nicIPv4.Address(); exists { + mask := 32 + if nicMask, exists := nicIPv4.Netmask(); exists { + mask, err = utils.MaskToBits(nicMask) + if err != nil { + return fmt.Errorf("mask to bits: %s", err) } } + ipv4Address := fmt.Sprintf("%s/%d", nicAddress, mask) + nicID2IPv4[nicID] = ipv4Address } - } else { - if os, exists := vm.Os(); exists { - if ovirtOsType, exists := os.Type(); exists { - vmOsType = ovirtOsType - } - if ovirtOsVersion, exists := os.Version(); exists { - if osFullVersion, exists := ovirtOsVersion.FullVersion(); exists { - vmOsVersion = osFullVersion + } + if nicIPv6, exists := nic.Ipv6(); exists { + if nicAddress, exists := nicIPv6.Address(); exists { + mask := 128 + if nicMask, exists := nicIPv6.Netmask(); exists { + mask, err = utils.MaskToBits(nicMask) + if err != nil { + return fmt.Errorf("mask to bits: %s", err) } } + ipv6Address := fmt.Sprintf("%s/%d", nicAddress, mask) + nicID2IPv6[nicID] = ipv6Address } } - platformName := utils.GeneratePlatformName(vmOsType, vmOsVersion) - vmPlatform, err := nbi.AddPlatform(&objects.Platform{ - Name: platformName, - Slug: utils.Slugify(platformName), - }) + + processedNicsIDs[nicID] = true + nicID2nic[nicID] = newInterface + } + return nil +} + +func (o *OVirtSource) syncVms(nbi *inventory.NetboxInventory) error { + for vmID, ovirtVM := range o.Vms { + collectedVM, err := o.extractVMData(nbi, vmID, ovirtVM) if err != nil { - return fmt.Errorf("failed adding oVirt vm's Platform %v with error: %s", vmPlatform, err) + return err } - newVM, err := nbi.AddVM(&objects.VM{ - NetboxObject: objects.NetboxObject{ - Tags: o.Config.SourceTags, - CustomFields: map[string]string{ - constants.CustomFieldSourceName: o.SourceConfig.Name, - }, - }, - Name: vmName, - Cluster: vmCluster, - Site: vmSite, - Tenant: vmTenant, - TenantGroup: vmTenantGroup, - Status: vmStatus, - Host: vmHostDevice, - Platform: vmPlatform, - Comments: vmComments, - VCPUs: vmVCPUs, - Memory: int(vmMemorySizeBytes / constants.KiB / constants.KiB), // MBs - Disk: int(vmDiskSizeBytes / constants.KiB / constants.KiB / constants.KiB), // GBs - }) + nbVM, err := nbi.AddVM(collectedVM) if err != nil { return fmt.Errorf("failed to sync oVirt vm: %v", err) } - err = o.syncVMInterfaces(nbi, vm, newVM) + err = o.syncVMInterfaces(nbi, ovirtVM, nbVM) if err != nil { return fmt.Errorf("failed to sync oVirt vm's interfaces: %v", err) } @@ -751,8 +626,149 @@ func (o *Source) syncVms(nbi *inventory.NetboxInventory) error { return nil } +func (o *OVirtSource) extractVMData(nbi *inventory.NetboxInventory, vmID string, vm *ovirtsdk4.Vm) (*objects.VM, error) { + // VM name, which is used as unique identifier for VMs in Netbox + vmName, exists := vm.Name() + if !exists { + o.Logger.Warning("name for oVirt vm with id ", vmID, " is empty. VM has to have unique name to be synced to netbox. Skipping...") + } + + // VM's Cluster + var vmCluster *objects.Cluster + cluster, exists := vm.Cluster() + if exists { + if _, ok := o.Clusters[cluster.MustId()]; ok { + vmCluster = nbi.ClustersIndexByName[o.Clusters[cluster.MustId()].MustName()] + } + } + + // Get VM's site,tenant and platform from cluster + var vmTenantGroup *objects.TenantGroup + var vmTenant *objects.Tenant + var vmSite *objects.Site + if vmCluster != nil { + vmTenantGroup = vmCluster.TenantGroup + vmTenant = vmCluster.Tenant + vmSite = vmCluster.Site + } + + // VM's Status + var vmStatus *objects.VMStatus + status, exists := vm.Status() + if exists { + switch status { + case ovirtsdk4.VMSTATUS_UP: + vmStatus = &objects.VMStatusActive + default: + vmStatus = &objects.VMStatusOffline + } + } + + // VM's Host Device (server) + var vmHostDevice *objects.Device + if host, exists := vm.Host(); exists { + if oHost, ok := o.Hosts[host.MustId()]; ok { + if oHostName, ok := oHost.Name(); ok { + vmHostDevice = nbi.DevicesIndexByNameAndSiteID[oHostName][vmSite.ID] + } + } + } + + // vmVCPUs + var vmVCPUs float32 + if cpuData, exists := vm.Cpu(); exists { + if cpuTopology, exists := cpuData.Topology(); exists { + if cores, exists := cpuTopology.Cores(); exists { + vmVCPUs = float32(cores) + } + } + } + + // Memory + var vmMemorySizeBytes int64 + if memory, exists := vm.Memory(); exists { + vmMemorySizeBytes = memory + } + + // Disks + var vmDiskSizeBytes int64 + if diskAttachment, exists := vm.DiskAttachments(); exists { + for _, diskAttachment := range diskAttachment.Slice() { + if ovirtDisk, exists := diskAttachment.Disk(); exists { + disk := o.Disks[ovirtDisk.MustId()] + if provisionedDiskSize, exists := disk.ProvisionedSize(); exists { + vmDiskSizeBytes += provisionedDiskSize + } + } + } + } + + // VM's comments + var vmComments string + if comments, exists := vm.Comment(); exists { + vmComments = comments + } + + // VM's Platform + var vmPlatform *objects.Platform + vmOsType := "Generic OS" + vmOsVersion := "Generic Version" + if guestOs, exists := vm.GuestOperatingSystem(); exists { + if guestOsType, exists := guestOs.Distribution(); exists { + vmOsType = guestOsType + } + if guestOsKernel, exists := guestOs.Kernel(); exists { + if guestOsVersion, exists := guestOsKernel.Version(); exists { + if osFullVersion, exists := guestOsVersion.FullVersion(); exists { + vmOsVersion = osFullVersion + } + } + } + } else { + if os, exists := vm.Os(); exists { + if ovirtOsType, exists := os.Type(); exists { + vmOsType = ovirtOsType + } + if ovirtOsVersion, exists := os.Version(); exists { + if osFullVersion, exists := ovirtOsVersion.FullVersion(); exists { + vmOsVersion = osFullVersion + } + } + } + } + platformName := utils.GeneratePlatformName(vmOsType, vmOsVersion) + vmPlatform, err := nbi.AddPlatform(&objects.Platform{ + Name: platformName, + Slug: utils.Slugify(platformName), + }) + if err != nil { + return nil, fmt.Errorf("failed adding oVirt vm's Platform %v with error: %s", vmPlatform, err) + } + + return &objects.VM{ + NetboxObject: objects.NetboxObject{ + Tags: o.Config.SourceTags, + CustomFields: map[string]string{ + constants.CustomFieldSourceName: o.SourceConfig.Name, + }, + }, + Name: vmName, + Cluster: vmCluster, + Site: vmSite, + Tenant: vmTenant, + TenantGroup: vmTenantGroup, + Status: vmStatus, + Host: vmHostDevice, + Platform: vmPlatform, + Comments: vmComments, + VCPUs: vmVCPUs, + Memory: int(vmMemorySizeBytes / constants.KiB / constants.KiB), // MBs + Disk: int(vmDiskSizeBytes / constants.KiB / constants.KiB / constants.KiB), // GBs + }, nil +} + // Syncs VM's interfaces to Netbox. -func (o *Source) syncVMInterfaces(nbi *inventory.NetboxInventory, ovirtVM *ovirtsdk4.Vm, netboxVM *objects.VM) error { +func (o *OVirtSource) syncVMInterfaces(nbi *inventory.NetboxInventory, ovirtVM *ovirtsdk4.Vm, netboxVM *objects.VM) error { if reportedDevices, exist := ovirtVM.ReportedDevices(); exist { for _, reportedDevice := range reportedDevices.Slice() { if reportedDeviceType, exist := reportedDevice.Type(); exist { diff --git a/internal/source/source.go b/internal/source/source.go index 094dc110..91738568 100644 --- a/internal/source/source.go +++ b/internal/source/source.go @@ -45,9 +45,9 @@ func NewSource(config *parser.SourceConfig, logger *logger.Logger, netboxInvento switch config.Type { case constants.Ovirt: - return &ovirt.Source{Config: commonConfig}, nil + return &ovirt.OVirtSource{Config: commonConfig}, nil case constants.Vmware: - return &vmware.Source{Config: commonConfig}, nil + return &vmware.VmwareSource{Config: commonConfig}, nil case constants.Dnac: return &dnac.Source{Config: commonConfig}, nil default: diff --git a/internal/source/vmware/vmware.go b/internal/source/vmware/vmware.go index 9043f992..13216ea9 100644 --- a/internal/source/vmware/vmware.go +++ b/internal/source/vmware/vmware.go @@ -18,8 +18,10 @@ import ( "github.com/vmware/govmomi/vim25/mo" ) -// Source represents an vsphere source. -type Source struct { +// VmwareSource represents an vsphere source. +// +//nolint:revive +type VmwareSource struct { common.Config // Vmware API data initialized in init functions Disks map[string]mo.Datastore @@ -83,7 +85,7 @@ type HostPortgroupData struct { nics []string } -func (vc *Source) Init() error { +func (vc *VmwareSource) Init() error { // Initialize regex relations vc.Logger.Debug("Initializing regex relations for oVirt source ", vc.SourceConfig.Name) vc.HostSiteRelations = utils.ConvertStringsToRegexPairs(vc.SourceConfig.HostSiteRelations) @@ -196,7 +198,7 @@ func (vc *Source) Init() error { } // Function that syncs all data from oVirt to Netbox. -func (vc *Source) Sync(nbi *inventory.NetboxInventory) error { +func (vc *VmwareSource) Sync(nbi *inventory.NetboxInventory) error { syncFunctions := []func(*inventory.NetboxInventory) error{ vc.syncNetworks, vc.syncDatacenters, @@ -218,7 +220,7 @@ func (vc *Source) Sync(nbi *inventory.NetboxInventory) error { // Currently we have to traverse the vsphere tree to get datacenter to cluster relation // For other objects relations are available in with containerView. -func (vc *Source) CreateClusterDataCenterRelation(ctx context.Context, client *vim25.Client) error { +func (vc *VmwareSource) CreateClusterDataCenterRelation(ctx context.Context, client *vim25.Client) error { finder := find.NewFinder(client, true) datacenters, err := finder.DatacenterList(ctx, "*") if err != nil { @@ -239,7 +241,7 @@ func (vc *Source) CreateClusterDataCenterRelation(ctx context.Context, client *v } // Creates a map of custom field ids to their names. -func (vc *Source) CreateCustomFieldRelation(ctx context.Context, client *vim25.Client) error { +func (vc *VmwareSource) CreateCustomFieldRelation(ctx context.Context, client *vim25.Client) error { cfm, err := object.GetCustomFieldsManager(client) if err != nil { return fmt.Errorf("createCustomFieldRelation: %s", err) diff --git a/internal/source/vmware/vmware_init.go b/internal/source/vmware/vmware_init.go index afffd3c2..b4e0417b 100644 --- a/internal/source/vmware/vmware_init.go +++ b/internal/source/vmware/vmware_init.go @@ -11,7 +11,7 @@ import ( ) // In vsphere we get vlans from DistributedVirtualPortgroups. -func (vc *Source) InitNetworks(ctx context.Context, containerView *view.ContainerView) error { +func (vc *VmwareSource) InitNetworks(ctx context.Context, containerView *view.ContainerView) error { var dvpgs []mo.DistributedVirtualPortgroup err := containerView.Retrieve(ctx, []string{"DistributedVirtualPortgroup"}, []string{"config"}, &dvpgs) if err != nil { @@ -77,7 +77,7 @@ func (vc *Source) InitNetworks(ctx context.Context, containerView *view.Containe return nil } -func (vc *Source) InitDisks(ctx context.Context, containerView *view.ContainerView) error { +func (vc *VmwareSource) InitDisks(ctx context.Context, containerView *view.ContainerView) error { var disks []mo.Datastore err := containerView.Retrieve(ctx, []string{"Datastore"}, []string{"summary", "host", "vm"}, &disks) if err != nil { @@ -90,7 +90,7 @@ func (vc *Source) InitDisks(ctx context.Context, containerView *view.ContainerVi return nil } -func (vc *Source) InitDataCenters(ctx context.Context, containerView *view.ContainerView) error { +func (vc *VmwareSource) InitDataCenters(ctx context.Context, containerView *view.ContainerView) error { var datacenters []mo.Datacenter err := containerView.Retrieve(ctx, []string{"Datacenter"}, []string{"name"}, &datacenters) if err != nil { @@ -103,7 +103,7 @@ func (vc *Source) InitDataCenters(ctx context.Context, containerView *view.Conta return nil } -func (vc *Source) InitClusters(ctx context.Context, containerView *view.ContainerView) error { +func (vc *VmwareSource) InitClusters(ctx context.Context, containerView *view.ContainerView) error { var clusters []mo.ClusterComputeResource err := containerView.Retrieve(ctx, []string{"ClusterComputeResource"}, []string{"summary", "host", "name"}, &clusters) if err != nil { @@ -120,7 +120,7 @@ func (vc *Source) InitClusters(ctx context.Context, containerView *view.Containe return nil } -func (vc *Source) InitHosts(ctx context.Context, containerView *view.ContainerView) error { +func (vc *VmwareSource) InitHosts(ctx context.Context, containerView *view.ContainerView) error { var hosts []mo.HostSystem err := containerView.Retrieve(ctx, []string{"HostSystem"}, []string{"name", "summary.host", "summary.hardware", "summary.runtime", "summary.config", "vm", "config.network"}, &hosts) if err != nil { @@ -180,7 +180,7 @@ func (vc *Source) InitHosts(ctx context.Context, containerView *view.ContainerVi return nil } -func (vc *Source) InitVms(ctx context.Context, containerView *view.ContainerView) error { +func (vc *VmwareSource) InitVms(ctx context.Context, containerView *view.ContainerView) error { var vms []mo.VirtualMachine err := containerView.Retrieve(ctx, []string{"VirtualMachine"}, []string{"summary", "name", "runtime", "guest", "config.hardware", "config.guestFullName"}, &vms) if err != nil { diff --git a/internal/source/vmware/vmware_sync.go b/internal/source/vmware/vmware_sync.go index 5215337f..2e90c2ab 100644 --- a/internal/source/vmware/vmware_sync.go +++ b/internal/source/vmware/vmware_sync.go @@ -15,7 +15,7 @@ import ( "github.com/vmware/govmomi/vim25/types" ) -func (vc *Source) syncNetworks(nbi *inventory.NetboxInventory) error { +func (vc *VmwareSource) syncNetworks(nbi *inventory.NetboxInventory) error { for _, dvpg := range vc.Networks.DistributedVirtualPortgroups { // TODO: currently we are syncing only vlans // Get vlanGroup from relations @@ -50,7 +50,7 @@ func (vc *Source) syncNetworks(nbi *inventory.NetboxInventory) error { return nil } -func (vc *Source) syncDatacenters(nbi *inventory.NetboxInventory) error { +func (vc *VmwareSource) syncDatacenters(nbi *inventory.NetboxInventory) error { for _, dc := range vc.DataCenters { nbClusterGroup := &objects.ClusterGroup{ NetboxObject: objects.NetboxObject{ @@ -71,7 +71,7 @@ func (vc *Source) syncDatacenters(nbi *inventory.NetboxInventory) error { return nil } -func (vc *Source) syncClusters(nbi *inventory.NetboxInventory) error { +func (vc *VmwareSource) syncClusters(nbi *inventory.NetboxInventory) error { clusterType := &objects.ClusterType{ NetboxObject: objects.NetboxObject{ Tags: vc.Config.SourceTags, @@ -146,7 +146,7 @@ func (vc *Source) syncClusters(nbi *inventory.NetboxInventory) error { // Host in vmware is a represented as device in netbox with a // custom role Server. -func (vc *Source) syncHosts(nbi *inventory.NetboxInventory) error { +func (vc *VmwareSource) syncHosts(nbi *inventory.NetboxInventory) error { for hostID, host := range vc.Hosts { var err error hostName := host.Name @@ -258,7 +258,12 @@ func (vc *Source) syncHosts(nbi *inventory.NetboxInventory) error { return nil } -func (vc *Source) syncHostNics(nbi *inventory.NetboxInventory, vcHost mo.HostSystem, nbHost *objects.Device) error { +func (vc *VmwareSource) syncHostNics(nbi *inventory.NetboxInventory, vcHost mo.HostSystem, nbHost *objects.Device) error { + // Variable for storeing all ipAddresses from all host interfaces, + // we use them to determine the primary ip of the host. + hostIPv4Addresses := []*objects.IPAddress{} + hostIPv6Addresses := []*objects.IPAddress{} + // Sync host's physical interfaces err := vc.syncHostPhysicalNics(nbi, vcHost, nbHost) if err != nil { @@ -266,15 +271,21 @@ func (vc *Source) syncHostNics(nbi *inventory.NetboxInventory, vcHost mo.HostSys } // Sync host's virtual interfaces - err = vc.syncHostVirtualNics(nbi, vcHost, nbHost) + err = vc.syncHostVirtualNics(nbi, vcHost, nbHost, hostIPv4Addresses, hostIPv6Addresses) if err != nil { return fmt.Errorf("virtual interfaces sync: %s", err) } + // Set host's private ip address from collected ips + err = vc.setHostPrimaryIPAddress(nbi, nbHost, hostIPv4Addresses, hostIPv6Addresses) + if err != nil { + return fmt.Errorf("adding host primary ip addresses: %s", err) + } + return nil } -func (vc *Source) syncHostPhysicalNics(nbi *inventory.NetboxInventory, vcHost mo.HostSystem, nbHost *objects.Device) error { +func (vc *VmwareSource) syncHostPhysicalNics(nbi *inventory.NetboxInventory, vcHost mo.HostSystem, nbHost *objects.Device) error { // Collect data from physical interfaces for _, pnic := range vcHost.Config.Network.Pnic { pnicName := pnic.Device @@ -418,108 +429,19 @@ func (vc *Source) syncHostPhysicalNics(nbi *inventory.NetboxInventory, vcHost mo return nil } -func (vc *Source) syncHostVirtualNics(nbi *inventory.NetboxInventory, vcHost mo.HostSystem, nbHost *objects.Device) error { +func (vc *VmwareSource) syncHostVirtualNics(nbi *inventory.NetboxInventory, vcHost mo.HostSystem, nbHost *objects.Device, hostIPv4Addresses []*objects.IPAddress, hostIPv6Addresses []*objects.IPAddress) error { // Collect data over all virtual interfaces for _, vnic := range vcHost.Config.Network.Vnic { - vnicName := vnic.Device - vnicPortgroupData, vnicPortgroupDataOk := vc.Networks.HostPortgroups[vcHost.Name][vnic.Portgroup] - vnicDvPortgroupKey := "" - if vnic.Spec.DistributedVirtualPort != nil { - vnicDvPortgroupKey = vnic.Spec.DistributedVirtualPort.PortgroupKey - } - vnicDvPortgroupData, vnicDvPortgroupDataOk := vc.Networks.DistributedVirtualPortgroups[vnicDvPortgroupKey] - vnicPortgroupVlanID := 0 - vnicDvPortgroupVlanIDs := []int{} - var vnicMode *objects.InterfaceMode - var vlanDescription, vnicDescription string - - // Get data from local portgroup, or distributed portgroup - if vnicPortgroupDataOk { - vnicPortgroupVlanID = vnicPortgroupData.vlanID - vnicSwitch := vnicPortgroupData.vswitch - vnicDescription = fmt.Sprintf("%s (%s, vlan ID: %d)", vnic.Portgroup, vnicSwitch, vnicPortgroupVlanID) - } else if vnicDvPortgroupDataOk { - vnicDescription = vnicDvPortgroupData.Name - vnicDvPortgroupVlanIDs = vnicDvPortgroupData.VlanIDs - if len(vnicDvPortgroupVlanIDs) == 1 && vnicDvPortgroupData.VlanIDs[0] == 4095 { - vnicDescription = "all vlans" - vnicMode = &objects.InterfaceModeTaggedAll - } else { - if len(vnicDvPortgroupData.VlanIDRanges) > 0 { - vlanDescription = fmt.Sprintf("vlan IDs: %s", strings.Join(vnicDvPortgroupData.VlanIDRanges, ",")) - } else { - vlanDescription = fmt.Sprintf("vlan ID: %d", vnicDvPortgroupData.VlanIDs[0]) - } - if len(vnicDvPortgroupData.VlanIDs) == 1 && vnicDvPortgroupData.VlanIDs[0] == 0 { - vnicMode = &objects.InterfaceModeAccess - } else { - vnicMode = &objects.InterfaceModeTagged - } - } - vnicDvPortgroupDwSwitchUUID := vnic.Spec.DistributedVirtualPort.SwitchUuid - vnicVswitch, vnicVswitchOk := vc.Networks.HostVirtualSwitches[vcHost.Name][vnicDvPortgroupDwSwitchUUID] - if vnicVswitchOk { - vnicDescription = fmt.Sprintf("%s (%v, %s)", vnicDescription, vnicVswitch, vlanDescription) - } - } - - var vnicUntaggedVlan *objects.Vlan - var vnicTaggedVlans []*objects.Vlan - if vnicPortgroupData != nil && vnicPortgroupVlanID != 0 { - vnicUntaggedVlanGroup, err := common.MatchVlanToGroup(nbi, vc.Networks.Vid2Name[vnicPortgroupVlanID], vc.VlanGroupRelations) - if err != nil { - return fmt.Errorf("vlan group: %s", err) - } - vnicUntaggedVlan = nbi.VlansIndexByVlanGroupIDAndVID[vnicUntaggedVlanGroup.ID][vnicPortgroupVlanID] - vnicMode = &objects.InterfaceModeAccess - // vnicUntaggedVlan = &objects.Vlan{ - // Name: fmt.Sprintf("ESXi %s (ID: %d) (%s)", vnic.Portgroup, vnicPortgroupVlanId, nbHost.Site.Name), - // Vid: vnicPortgroupVlanId, - // Tenant: nbHost.Tenant, - // } - } else if vnicDvPortgroupData != nil { - for _, vnicDvPortgroupDataVlanID := range vnicDvPortgroupVlanIDs { - if vnicMode != &objects.InterfaceModeTagged { - break - } - if vnicDvPortgroupDataVlanID == 0 { - continue - } - vnicTaggedVlanGroup, err := common.MatchVlanToGroup(nbi, vc.Networks.Vid2Name[vnicDvPortgroupDataVlanID], vc.VlanGroupRelations) - if err != nil { - return fmt.Errorf("vlan group: %s", err) - } - vnicTaggedVlans = append(vnicTaggedVlans, nbi.VlansIndexByVlanGroupIDAndVID[vnicTaggedVlanGroup.ID][vnicDvPortgroupDataVlanID]) - // vnicTaggedVlans = append(vnicTaggedVlans, &objects.Vlan{ - // Name: fmt.Sprintf("%s-%d", vnicDvPortgroupData.Name, vnicDvPortgroupDataVlanId), - // Vid: vnicDvPortgroupDataVlanId, - // Tenant: nbHost.Tenant, - // }) - } + hostVnic, err := vc.collectHostVirtualNicData(nbi, nbHost, vcHost, vnic) + if err != nil { + return err } - nbVnic, err := nbi.AddInterface(&objects.Interface{ - NetboxObject: objects.NetboxObject{ - Tags: vc.Config.SourceTags, - Description: vnicDescription, - CustomFields: map[string]string{ - constants.CustomFieldSourceName: vc.SourceConfig.Name, - }, - }, - Device: nbHost, - Name: vnicName, - Status: true, - Type: &objects.VirtualInterfaceType, - MTU: int(vnic.Spec.Mtu), - Mode: vnicMode, - TaggedVlans: vnicTaggedVlans, - UntaggedVlan: vnicUntaggedVlan, - }) + nbVnic, err := nbi.AddInterface(hostVnic) if err != nil { return err } - var nbIPAddress *objects.IPAddress // Get IPv4 address for this vnic. TODO: filter ipv4Address := vnic.Spec.Ip.IpAddress ipv4MaskBits, err := utils.MaskToBits(vnic.Spec.Ip.SubnetMask) @@ -527,7 +449,7 @@ func (vc *Source) syncHostVirtualNics(nbi *inventory.NetboxInventory, vcHost mo. return fmt.Errorf("mask to bits: %s", err) } ipv4DNS := utils.ReverseLookup(ipv4Address) - nbIPAddress, err = nbi.AddIPAddress(&objects.IPAddress{ + nbIPv4Address, err := nbi.AddIPAddress(&objects.IPAddress{ NetboxObject: objects.NetboxObject{ Tags: vc.Config.SourceTags, CustomFields: map[string]string{ @@ -544,14 +466,14 @@ func (vc *Source) syncHostVirtualNics(nbi *inventory.NetboxInventory, vcHost mo. if err != nil { return err } + hostIPv4Addresses = append(hostIPv4Addresses, nbIPv4Address) - var nbIPv6Address *objects.IPAddress if vnic.Spec.Ip.IpV6Config != nil { for _, ipv6Entry := range vnic.Spec.Ip.IpV6Config.IpV6Address { ipv6Address := ipv6Entry.IpAddress ipv6Mask := ipv6Entry.PrefixLength // TODO: Filter out ipv6 addresses - nbIPv6Address, err = nbi.AddIPAddress(&objects.IPAddress{ + nbIPv6Address, err := nbi.AddIPAddress(&objects.IPAddress{ NetboxObject: objects.NetboxObject{ Tags: vc.Config.SourceTags, CustomFields: map[string]string{ @@ -567,33 +489,136 @@ func (vc *Source) syncHostVirtualNics(nbi *inventory.NetboxInventory, vcHost mo. if err != nil { return err } + hostIPv6Addresses = append(hostIPv6Addresses, nbIPv6Address) } } + } + return nil +} - // Update host's primary ipv4: TODO, determine if primary or not - if nbHost.PrimaryIPv4 == nil && nbIPAddress != nil { - newNbHost := *nbHost - newNbHost.PrimaryIPv4 = nbIPAddress - nbHost, err = nbi.AddDevice(&newNbHost) - if err != nil { - return fmt.Errorf("new Host's primaryIpv4: %s", err) +func (vc *VmwareSource) setHostPrimaryIPAddress(nbi *inventory.NetboxInventory, nbHost *objects.Device, hostIPv4Addresses []*objects.IPAddress, hostIPv6Addresses []*objects.IPAddress) error { + if len(hostIPv4Addresses) > 0 || len(hostIPv6Addresses) > 0 { + var hostPrimaryIPv4 *objects.IPAddress + for _, addr := range hostIPv4Addresses { + if hostPrimaryIPv4 == nil || utils.Lookup(nbHost.Name) == addr.Address { + hostPrimaryIPv4 = addr + } + } + var hostPrimaryIPv6 *objects.IPAddress + for _, addr := range hostIPv6Addresses { + if hostPrimaryIPv6 == nil || utils.Lookup(nbHost.Name) == addr.Address { + hostPrimaryIPv6 = addr } } + newHost := *nbHost + newHost.PrimaryIPv4 = hostPrimaryIPv4 + newHost.PrimaryIPv6 = hostPrimaryIPv6 + _, err := nbi.AddDevice(&newHost) + if err != nil { + return fmt.Errorf("updating host's primary ip: %s", err) + } + } + + return nil +} + +func (vc *VmwareSource) collectHostVirtualNicData(nbi *inventory.NetboxInventory, nbHost *objects.Device, vcHost mo.HostSystem, vnic types.HostVirtualNic) (*objects.Interface, error) { + vnicName := vnic.Device + vnicPortgroupData, vnicPortgroupDataOk := vc.Networks.HostPortgroups[vcHost.Name][vnic.Portgroup] + vnicDvPortgroupKey := "" + if vnic.Spec.DistributedVirtualPort != nil { + vnicDvPortgroupKey = vnic.Spec.DistributedVirtualPort.PortgroupKey + } + vnicDvPortgroupData, vnicDvPortgroupDataOk := vc.Networks.DistributedVirtualPortgroups[vnicDvPortgroupKey] + vnicPortgroupVlanID := 0 + vnicDvPortgroupVlanIDs := []int{} + var vnicMode *objects.InterfaceMode + var vlanDescription, vnicDescription string + + // Get data from local portgroup, or distributed portgroup + if vnicPortgroupDataOk { + vnicPortgroupVlanID = vnicPortgroupData.vlanID + vnicSwitch := vnicPortgroupData.vswitch + vnicDescription = fmt.Sprintf("%s (%s, vlan ID: %d)", vnic.Portgroup, vnicSwitch, vnicPortgroupVlanID) + } else if vnicDvPortgroupDataOk { + vnicDescription = vnicDvPortgroupData.Name + vnicDvPortgroupVlanIDs = vnicDvPortgroupData.VlanIDs + if len(vnicDvPortgroupVlanIDs) == 1 && vnicDvPortgroupData.VlanIDs[0] == 4095 { + vnicDescription = "all vlans" + vnicMode = &objects.InterfaceModeTaggedAll + } else { + if len(vnicDvPortgroupData.VlanIDRanges) > 0 { + vlanDescription = fmt.Sprintf("vlan IDs: %s", strings.Join(vnicDvPortgroupData.VlanIDRanges, ",")) + } else { + vlanDescription = fmt.Sprintf("vlan ID: %d", vnicDvPortgroupData.VlanIDs[0]) + } + if len(vnicDvPortgroupData.VlanIDs) == 1 && vnicDvPortgroupData.VlanIDs[0] == 0 { + vnicMode = &objects.InterfaceModeAccess + } else { + vnicMode = &objects.InterfaceModeTagged + } + } + vnicDvPortgroupDwSwitchUUID := vnic.Spec.DistributedVirtualPort.SwitchUuid + vnicVswitch, vnicVswitchOk := vc.Networks.HostVirtualSwitches[vcHost.Name][vnicDvPortgroupDwSwitchUUID] + if vnicVswitchOk { + vnicDescription = fmt.Sprintf("%s (%v, %s)", vnicDescription, vnicVswitch, vlanDescription) + } + } - // Update host's primary ipv4: TODO, determine if primary or not - if nbHost.PrimaryIPv6 == nil && nbIPv6Address != nil { - newNbHost := *nbHost - newNbHost.PrimaryIPv6 = nbIPv6Address - nbHost, err = nbi.AddDevice(&newNbHost) + var vnicUntaggedVlan *objects.Vlan + var vnicTaggedVlans []*objects.Vlan + if vnicPortgroupData != nil && vnicPortgroupVlanID != 0 { + vnicUntaggedVlanGroup, err := common.MatchVlanToGroup(nbi, vc.Networks.Vid2Name[vnicPortgroupVlanID], vc.VlanGroupRelations) + if err != nil { + return nil, fmt.Errorf("vlan group: %s", err) + } + vnicUntaggedVlan = nbi.VlansIndexByVlanGroupIDAndVID[vnicUntaggedVlanGroup.ID][vnicPortgroupVlanID] + vnicMode = &objects.InterfaceModeAccess + // vnicUntaggedVlan = &objects.Vlan{ + // Name: fmt.Sprintf("ESXi %s (ID: %d) (%s)", vnic.Portgroup, vnicPortgroupVlanId, nbHost.Site.Name), + // Vid: vnicPortgroupVlanId, + // Tenant: nbHost.Tenant, + // } + } else if vnicDvPortgroupData != nil { + for _, vnicDvPortgroupDataVlanID := range vnicDvPortgroupVlanIDs { + if vnicMode != &objects.InterfaceModeTagged { + break + } + if vnicDvPortgroupDataVlanID == 0 { + continue + } + vnicTaggedVlanGroup, err := common.MatchVlanToGroup(nbi, vc.Networks.Vid2Name[vnicDvPortgroupDataVlanID], vc.VlanGroupRelations) if err != nil { - return fmt.Errorf("new Host's primaryIpv6: %s", err) + return nil, fmt.Errorf("vlan group: %s", err) } + vnicTaggedVlans = append(vnicTaggedVlans, nbi.VlansIndexByVlanGroupIDAndVID[vnicTaggedVlanGroup.ID][vnicDvPortgroupDataVlanID]) + // vnicTaggedVlans = append(vnicTaggedVlans, &objects.Vlan{ + // Name: fmt.Sprintf("%s-%d", vnicDvPortgroupData.Name, vnicDvPortgroupDataVlanId), + // Vid: vnicDvPortgroupDataVlanId, + // Tenant: nbHost.Tenant, + // }) } } - return nil + return &objects.Interface{ + NetboxObject: objects.NetboxObject{ + Tags: vc.Config.SourceTags, + Description: vnicDescription, + CustomFields: map[string]string{ + constants.CustomFieldSourceName: vc.SourceConfig.Name, + }, + }, + Device: nbHost, + Name: vnicName, + Status: true, + Type: &objects.VirtualInterfaceType, + MTU: int(vnic.Spec.Mtu), + Mode: vnicMode, + TaggedVlans: vnicTaggedVlans, + UntaggedVlan: vnicUntaggedVlan, + }, nil } -func (vc *Source) syncVms(nbi *inventory.NetboxInventory) error { +func (vc *VmwareSource) syncVms(nbi *inventory.NetboxInventory) error { for vmKey, vm := range vc.Vms { // Check if vm is a template, we don't add templates into netbox. if vm.Config != nil { @@ -730,44 +755,9 @@ func (vc *Source) syncVms(nbi *inventory.NetboxInventory) error { return fmt.Errorf("failed to sync vmware vm: %v", err) } - // If vm owner name was found we also add contact assignment to the vm - var vmMailMapFallback bool - if len(vmOwners) > 0 && len(vmOwnerEmails) > 0 && len(vmOwners) != len(vmOwnerEmails) { - vc.Logger.Warningf("vm owner names and emails mismatch (len(vmOwnerEmails) != len(vmOwners), using fallback mechanism") - vmMailMapFallback = true - } - vmOwner2Email := utils.MatchNamesWithEmails(vmOwners, vmOwnerEmails, vc.Logger) - for i, vmOwnerName := range vmOwners { - if vmOwnerName != "" { - var vmOwnerEmail string - if len(vmOwnerEmails) > 0 { - if vmMailMapFallback { - if match, ok := vmOwner2Email[vmOwnerName]; ok { - vmOwnerEmail = match - } - } else { - vmOwnerEmail = vmOwnerEmails[i] - } - } - contact, err := nbi.AddContact( - &objects.Contact{ - Name: strings.TrimSpace(vmOwners[i]), - Email: vmOwnerEmail, - }, - ) - if err != nil { - return fmt.Errorf("creating vm contact: %s", err) - } - _, err = nbi.AddContactAssignment(&objects.ContactAssignment{ - ContentType: "virtualization.virtualmachine", - ObjectID: newVM.ID, - Contact: contact, - Role: nbi.ContactRolesIndexByName[objects.AdminContactRoleName], - }) - if err != nil { - return fmt.Errorf("add contact assignment for vm: %s", err) - } - } + err = vc.addVMContact(nbi, newVM, vmOwners, vmOwnerEmails) + if err != nil { + return fmt.Errorf("adding vm's contact: %s", err) } // Sync vm interfaces @@ -780,9 +770,12 @@ func (vc *Source) syncVms(nbi *inventory.NetboxInventory) error { } // Syncs VM's interfaces to Netbox. -func (vc *Source) syncVMInterfaces(nbi *inventory.NetboxInventory, vmwareVM mo.VirtualMachine, netboxVM *objects.VM) error { +func (vc *VmwareSource) syncVMInterfaces(nbi *inventory.NetboxInventory, vmwareVM mo.VirtualMachine, netboxVM *objects.VM) error { + // Data to determine the primary IP address of the vm var vmDefaultGatewayIpv4 string var vmDefaultGatewayIpv6 string + vmIPv4Addresses := make([]*objects.IPAddress, 0) + vmIPv6Addresses := make([]*objects.IPAddress, 0) // From vm's routing determine the default interface if len(vmwareVM.Guest.IpStack) > 0 { @@ -834,218 +827,280 @@ func (vc *Source) syncVMInterfaces(nbi *inventory.NetboxInventory, vmwareVM mo.V } if vmEthernetCard != nil { - intMac := vmEthernetCard.MacAddress - intConnected := vmEthernetCard.Connectable.Connected - intDeviceBackingInfo := vmEthernetCard.Backing - intDeviceInfo := vmEthernetCard.DeviceInfo - nicIPv4Addresses := []string{} - primaryIPv4Address := "" - nicIPv6Addresses := []string{} - primaryIPv6Address := "" - var intMtu int - var intNetworkName string - var intNetworkPrivate bool - var intMode *objects.VMInterfaceMode - intNetworkVlanIDs := []int{} - intNetworkVlanIDRanges := []string{} - - // Get info from local vSwitches if possible, else from DistributedPortGroup - if backingInfo, ok := intDeviceBackingInfo.(*types.VirtualEthernetCardNetworkBackingInfo); ok { - intNetworkName = backingInfo.DeviceName - intHostPgroup := vc.Networks.HostPortgroups[netboxVM.Host.Name][intNetworkName] - - if intHostPgroup != nil { - intNetworkVlanIDs = []int{intHostPgroup.vlanID} - intNetworkVlanIDRanges = []string{strconv.Itoa(intHostPgroup.vlanID)} - intVswitchName := intHostPgroup.vswitch - intVswitchData := vc.Networks.HostVirtualSwitches[netboxVM.Host.Name][intVswitchName] - if intVswitchData != nil { - intMtu = intVswitchData.mtu - } - } - } else if backingInfo, ok := intDeviceBackingInfo.(*types.VirtualEthernetCardDistributedVirtualPortBackingInfo); ok { - dvsPortgroupKey := backingInfo.Port.PortgroupKey - intPortgroupData := vc.Networks.DistributedVirtualPortgroups[dvsPortgroupKey] - - if intPortgroupData != nil { - intNetworkName = intPortgroupData.Name - intNetworkVlanIDs = intPortgroupData.VlanIDs - intNetworkVlanIDRanges = intPortgroupData.VlanIDRanges - if len(intNetworkVlanIDRanges) == 0 { - intNetworkVlanIDRanges = []string{strconv.Itoa(intNetworkVlanIDs[0])} - } - intNetworkPrivate = intPortgroupData.Private - } + nicIPv4Addresses, nicIPv6Addresses, collectedVMIface, err := vc.collectVMInterfaceData(nbi, netboxVM, vmwareVM, vmEthernetCard) + if err != nil { + return err + } - intDvswitchUUID := backingInfo.Port.SwitchUuid - intDvswitchData := vc.Networks.HostProxySwitches[netboxVM.Host.Name][intDvswitchUUID] + nbVMInterface, err := nbi.AddVMInterface(collectedVMIface) + if err != nil { + return fmt.Errorf("adding VmInterface: %s", err) + } - if intDvswitchData != nil { - intMtu = intDvswitchData.mtu - } + err = vc.addVMInterfaceIPs(nbi, nbVMInterface, nicIPv4Addresses, nicIPv6Addresses, vmIPv4Addresses, vmIPv6Addresses) + if err != nil { + return err } + } + } + err := vc.setVMPrimaryIPAddress(nbi, netboxVM, vmDefaultGatewayIpv4, vmDefaultGatewayIpv6, vmIPv4Addresses, vmIPv6Addresses) + if err != nil { + return fmt.Errorf("setting vm primary ip address: %s", err) + } + + return nil +} - var vlanDescription string - intLabel := intDeviceInfo.GetDescription().Label - splitStr := strings.Split(intLabel, " ") - intName := fmt.Sprintf("vNic %s", splitStr[len(splitStr)-1]) - intFullName := intName - if intNetworkName != "" { - intFullName = fmt.Sprintf("%s (%s)", intFullName, intNetworkName) +func (vc *VmwareSource) collectVMInterfaceData(nbi *inventory.NetboxInventory, netboxVM *objects.VM, vmwareVM mo.VirtualMachine, vmEthernetCard *types.VirtualEthernetCard) ([]string, []string, *objects.VMInterface, error) { + intMac := vmEthernetCard.MacAddress + intConnected := vmEthernetCard.Connectable.Connected + intDeviceBackingInfo := vmEthernetCard.Backing + intDeviceInfo := vmEthernetCard.DeviceInfo + nicIPv4Addresses := []string{} + nicIPv6Addresses := []string{} + var intMtu int + var intNetworkName string + var intNetworkPrivate bool + var intMode *objects.VMInterfaceMode + intNetworkVlanIDs := []int{} + intNetworkVlanIDRanges := []string{} + + // Get info from local vSwitches if possible, else from DistributedPortGroup + if backingInfo, ok := intDeviceBackingInfo.(*types.VirtualEthernetCardNetworkBackingInfo); ok { + intNetworkName = backingInfo.DeviceName + intHostPgroup := vc.Networks.HostPortgroups[netboxVM.Host.Name][intNetworkName] + + if intHostPgroup != nil { + intNetworkVlanIDs = []int{intHostPgroup.vlanID} + intNetworkVlanIDRanges = []string{strconv.Itoa(intHostPgroup.vlanID)} + intVswitchName := intHostPgroup.vswitch + intVswitchData := vc.Networks.HostVirtualSwitches[netboxVM.Host.Name][intVswitchName] + if intVswitchData != nil { + intMtu = intVswitchData.mtu } - intDescription := intLabel - if len(intNetworkVlanIDs) > 0 { - if len(intNetworkVlanIDs) == 1 && intNetworkVlanIDs[0] == 4095 { - vlanDescription = "all vlans" - intMode = &objects.VMInterfaceModeTaggedAll - } else { - vlanDescription = fmt.Sprintf("vlan ID: %s", strings.Join(intNetworkVlanIDRanges, ", ")) - if len(intNetworkVlanIDs) == 1 { - intMode = &objects.VMInterfaceModeAccess - } else { - intMode = &objects.VMInterfaceModeTagged - } - } + } + } else if backingInfo, ok := intDeviceBackingInfo.(*types.VirtualEthernetCardDistributedVirtualPortBackingInfo); ok { + dvsPortgroupKey := backingInfo.Port.PortgroupKey + intPortgroupData := vc.Networks.DistributedVirtualPortgroups[dvsPortgroupKey] + + if intPortgroupData != nil { + intNetworkName = intPortgroupData.Name + intNetworkVlanIDs = intPortgroupData.VlanIDs + intNetworkVlanIDRanges = intPortgroupData.VlanIDRanges + if len(intNetworkVlanIDRanges) == 0 { + intNetworkVlanIDRanges = []string{strconv.Itoa(intNetworkVlanIDs[0])} + } + intNetworkPrivate = intPortgroupData.Private + } - if intNetworkPrivate { - vlanDescription += "(private)" - } - intDescription = fmt.Sprintf("%s (%s)", intDescription, vlanDescription) + intDvswitchUUID := backingInfo.Port.SwitchUuid + intDvswitchData := vc.Networks.HostProxySwitches[netboxVM.Host.Name][intDvswitchUUID] + + if intDvswitchData != nil { + intMtu = intDvswitchData.mtu + } + } + + var vlanDescription string + intLabel := intDeviceInfo.GetDescription().Label + splitStr := strings.Split(intLabel, " ") + intName := fmt.Sprintf("vNic %s", splitStr[len(splitStr)-1]) + intFullName := intName + if intNetworkName != "" { + intFullName = fmt.Sprintf("%s (%s)", intFullName, intNetworkName) + } + intDescription := intLabel + if len(intNetworkVlanIDs) > 0 { + if len(intNetworkVlanIDs) == 1 && intNetworkVlanIDs[0] == 4095 { + vlanDescription = "all vlans" + intMode = &objects.VMInterfaceModeTaggedAll + } else { + vlanDescription = fmt.Sprintf("vlan ID: %s", strings.Join(intNetworkVlanIDRanges, ", ")) + if len(intNetworkVlanIDs) == 1 { + intMode = &objects.VMInterfaceModeAccess + } else { + intMode = &objects.VMInterfaceModeTagged } - // Find corresponding guest NIC and get IP addresses and connected status - for _, guestNic := range vmwareVM.Guest.Net { - if intMac != guestNic.MacAddress { - continue - } - intConnected = guestNic.Connected - - if guestNic.IpConfig != nil { - for _, intIP := range guestNic.IpConfig.IpAddress { - intIPAddress := fmt.Sprintf("%s/%d", intIP.IpAddress, intIP.PrefixLength) - ipVersion := utils.GetIPVersion(intIP.IpAddress) - switch ipVersion { - case constants.IPv4: - nicIPv4Addresses = append(nicIPv4Addresses, intIPAddress) - if vmDefaultGatewayIpv4 != "" && utils.SubnetContainsIPAddress(vmDefaultGatewayIpv4, intIPAddress) { - primaryIPv4Address = intIPAddress - } - case constants.IPv6: - nicIPv6Addresses = append(nicIPv6Addresses, intIPAddress) - if vmDefaultGatewayIpv6 != "" && utils.SubnetContainsIPAddress(vmDefaultGatewayIpv6, intIPAddress) { - primaryIPv6Address = intIPAddress - } - default: - return fmt.Errorf("unknown ip version: %s", intIPAddress) - } - } + } + + if intNetworkPrivate { + vlanDescription += "(private)" + } + intDescription = fmt.Sprintf("%s (%s)", intDescription, vlanDescription) + } + // Find corresponding guest NIC and get IP addresses and connected status + for _, guestNic := range vmwareVM.Guest.Net { + if intMac != guestNic.MacAddress { + continue + } + intConnected = guestNic.Connected + + if guestNic.IpConfig != nil { + for _, intIP := range guestNic.IpConfig.IpAddress { + intIPAddress := fmt.Sprintf("%s/%d", intIP.IpAddress, intIP.PrefixLength) + ipVersion := utils.GetIPVersion(intIP.IpAddress) + switch ipVersion { + case constants.IPv4: + nicIPv4Addresses = append(nicIPv4Addresses, intIPAddress) + case constants.IPv6: + nicIPv6Addresses = append(nicIPv6Addresses, intIPAddress) + default: + return nicIPv4Addresses, nicIPv6Addresses, nil, fmt.Errorf("unknown ip version: %s", intIPAddress) } } - var intUntaggedVlan *objects.Vlan - var intTaggedVlanList []*objects.Vlan - if len(intNetworkVlanIDs) > 0 && intMode != &objects.VMInterfaceModeTaggedAll { - if len(intNetworkVlanIDs) == 1 && intNetworkVlanIDs[0] != 0 { - vidID := intNetworkVlanIDs[0] - nicUntaggedVlanGroup, err := common.MatchVlanToGroup(nbi, vc.Networks.Vid2Name[vidID], vc.VlanGroupRelations) - if err != nil { - return fmt.Errorf("vlan group: %s", err) - } - intUntaggedVlan = nbi.VlansIndexByVlanGroupIDAndVID[nicUntaggedVlanGroup.ID][vidID] - } else { - intTaggedVlanList = []*objects.Vlan{} - for _, intNetworkVlanID := range intNetworkVlanIDs { - if intNetworkVlanID == 0 { - continue - } - // nicTaggedVlanList = append(nicTaggedVlanList, nbi.get[intNetworkVlanId]) - } + } + } + var intUntaggedVlan *objects.Vlan + var intTaggedVlanList []*objects.Vlan + if len(intNetworkVlanIDs) > 0 && intMode != &objects.VMInterfaceModeTaggedAll { + if len(intNetworkVlanIDs) == 1 && intNetworkVlanIDs[0] != 0 { + vidID := intNetworkVlanIDs[0] + nicUntaggedVlanGroup, err := common.MatchVlanToGroup(nbi, vc.Networks.Vid2Name[vidID], vc.VlanGroupRelations) + if err != nil { + return nicIPv4Addresses, nicIPv6Addresses, nil, fmt.Errorf("vlan group: %s", err) + } + intUntaggedVlan = nbi.VlansIndexByVlanGroupIDAndVID[nicUntaggedVlanGroup.ID][vidID] + } else { + intTaggedVlanList = []*objects.Vlan{} + for _, intNetworkVlanID := range intNetworkVlanIDs { + if intNetworkVlanID == 0 { + continue } + // nicTaggedVlanList = append(nicTaggedVlanList, nbi.get[intNetworkVlanId]) } - nbVMInterface, err := nbi.AddVMInterface(&objects.VMInterface{ - NetboxObject: objects.NetboxObject{ - Tags: vc.Config.SourceTags, - Description: intDescription, - CustomFields: map[string]string{ - constants.CustomFieldSourceName: vc.SourceConfig.Name, - }, + } + } + return nicIPv4Addresses, nicIPv6Addresses, &objects.VMInterface{ + NetboxObject: objects.NetboxObject{ + Tags: vc.Config.SourceTags, + Description: intDescription, + CustomFields: map[string]string{ + constants.CustomFieldSourceName: vc.SourceConfig.Name, + }, + }, + VM: netboxVM, + Name: intFullName, + MACAddress: strings.ToUpper(intMac), + MTU: intMtu, + Mode: intMode, + Enabled: intConnected, + TaggedVlans: intTaggedVlanList, + UntaggedVlan: intUntaggedVlan, + }, nil +} + +// Function that adds all collected IPs for the vm's interface to netbox. +func (vc *VmwareSource) addVMInterfaceIPs(nbi *inventory.NetboxInventory, nbVMInterface *objects.VMInterface, nicIPv4Addresses []string, nicIPv6Addresses []string, vmIPv4Addresses []*objects.IPAddress, vmIPv6Addresses []*objects.IPAddress) error { + // Add all collected ipv4 addresses for the interface to netbox + for _, ipv4Address := range nicIPv4Addresses { + nbIPv4Address, err := nbi.AddIPAddress(&objects.IPAddress{ + NetboxObject: objects.NetboxObject{ + Tags: vc.Config.SourceTags, + CustomFields: map[string]string{ + constants.CustomFieldSourceName: vc.SourceConfig.Name, }, - VM: netboxVM, - Name: intFullName, - MACAddress: strings.ToUpper(intMac), - MTU: intMtu, - Mode: intMode, - Enabled: intConnected, - TaggedVlans: intTaggedVlanList, - UntaggedVlan: intUntaggedVlan, - }) - if err != nil { - return fmt.Errorf("adding VmInterface: %s", err) - } + }, + Address: ipv4Address, + DNSName: utils.ReverseLookup(ipv4Address), + AssignedObjectType: objects.AssignedObjectTypeVMInterface, + AssignedObjectID: nbVMInterface.ID, + }) + if err != nil { + vc.Logger.Warningf("adding ipv4 address: %s", err) + } + vmIPv4Addresses = append(vmIPv4Addresses, nbIPv4Address) + } - // Setup Primary ipv4 address - var nbPrimaryIPv4 *objects.IPAddress - if primaryIPv4Address == "" { - // Fallback mechanism, we choose the first ipv4 address on the interface - if len(nicIPv4Addresses) > 0 { - primaryIPv4Address = nicIPv4Addresses[0] - } + // Add all collected ipv6 addresses for the interface to netbox + for _, ipv6Address := range nicIPv6Addresses { + nbIPv6Address, err := nbi.AddIPAddress(&objects.IPAddress{ + NetboxObject: objects.NetboxObject{ + Tags: vc.Config.SourceTags, + CustomFields: map[string]string{ + constants.CustomFieldSourceName: vc.SourceConfig.Name, + }, + }, + Address: ipv6Address, + DNSName: utils.ReverseLookup(ipv6Address), + AssignedObjectType: objects.AssignedObjectTypeVMInterface, + AssignedObjectID: nbVMInterface.ID, + }) + if err != nil { + vc.Logger.Warningf("adding ipv6 address: %s", err) + } + vmIPv6Addresses = append(vmIPv6Addresses, nbIPv6Address) + } + return nil +} + +// setVMPrimaryIPAddress updates the vm's primary IP in the following way: +// we loop through all of the collected IPv4 and IPv6 addresses for the vm. +// If any of the ips is in the same subnet as the default gateway, we choose it. +// If there is no ip in the subnet of the default gateway, we choose the first one. +func (vc *VmwareSource) setVMPrimaryIPAddress(nbi *inventory.NetboxInventory, netboxVM *objects.VM, vmDefaultGatewayIpv4 string, vmDefaultGatewayIpv6 string, vmIPv4Addresses []*objects.IPAddress, vmIPv6Addresses []*objects.IPAddress) error { + if len(vmIPv4Addresses) > 0 || len(vmIPv6Addresses) > 0 { + var vmIPv4PrimaryAddress *objects.IPAddress + for _, addr := range vmIPv4Addresses { + if vmIPv4PrimaryAddress == nil || utils.SubnetContainsIPAddress(vmDefaultGatewayIpv4, addr.Address) { + vmIPv4PrimaryAddress = addr } - if primaryIPv4Address != "" { - nbPrimaryIPv4, err = nbi.AddIPAddress(&objects.IPAddress{ - NetboxObject: objects.NetboxObject{ - Tags: vc.Config.SourceTags, - CustomFields: map[string]string{ - constants.CustomFieldSourceName: vc.SourceConfig.Name, - }, - }, - Address: primaryIPv4Address, - DNSName: utils.ReverseLookup(primaryIPv4Address), - AssignedObjectType: objects.AssignedObjectTypeVMInterface, - AssignedObjectID: nbVMInterface.ID, - }) - if err != nil { - vc.Logger.Errorf("adding ipv4 address: %s", err) - } + } + var vmIPv6PrimaryAddress *objects.IPAddress + for _, addr := range vmIPv6Addresses { + if vmIPv6PrimaryAddress == nil || utils.SubnetContainsIPAddress(vmDefaultGatewayIpv6, addr.Address) { + vmIPv6PrimaryAddress = addr } + } + newNetboxVM := *netboxVM + newNetboxVM.PrimaryIPv4 = vmIPv4PrimaryAddress + newNetboxVM.PrimaryIPv6 = vmIPv6PrimaryAddress + _, err := nbi.AddVM(&newNetboxVM) + if err != nil { + return fmt.Errorf("updating vm's primary ip: %s", err) + } + } + return nil +} - // Setup Primary ipv6 address - var nbPrimaryIPv6 *objects.IPAddress - if primaryIPv6Address != "" { - // Fallback mechanism, we choose the first ipv6 address on the interface - if len(nicIPv6Addresses) > 0 { - primaryIPv6Address = nicIPv6Addresses[0] +func (vc *VmwareSource) addVMContact(nbi *inventory.NetboxInventory, nbVM *objects.VM, vmOwners []string, vmOwnerEmails []string) error { + // If vm owner name was found we also add contact assignment to the vm + var vmMailMapFallback bool + if len(vmOwners) > 0 && len(vmOwnerEmails) > 0 && len(vmOwners) != len(vmOwnerEmails) { + vc.Logger.Warningf("vm owner names and emails mismatch (len(vmOwnerEmails) != len(vmOwners), using fallback mechanism") + vmMailMapFallback = true + } + vmOwner2Email := utils.MatchNamesWithEmails(vmOwners, vmOwnerEmails, vc.Logger) + for i, vmOwnerName := range vmOwners { + if vmOwnerName != "" { + var vmOwnerEmail string + if len(vmOwnerEmails) > 0 { + if vmMailMapFallback { + if match, ok := vmOwner2Email[vmOwnerName]; ok { + vmOwnerEmail = match + } + } else { + vmOwnerEmail = vmOwnerEmails[i] } } - if primaryIPv6Address != "" { - nbPrimaryIPv6, err = nbi.AddIPAddress(&objects.IPAddress{ - NetboxObject: objects.NetboxObject{ - Tags: vc.Config.SourceTags, - CustomFields: map[string]string{ - constants.CustomFieldSourceName: vc.SourceConfig.Name, - }, - }, - Address: primaryIPv6Address, - DNSName: utils.ReverseLookup(primaryIPv6Address), - AssignedObjectType: objects.AssignedObjectTypeVMInterface, - AssignedObjectID: nbVMInterface.ID, - }) - if err != nil { - vc.Logger.Errorf("adding ipv6 address: %s", err) - } + contact, err := nbi.AddContact( + &objects.Contact{ + Name: strings.TrimSpace(vmOwners[i]), + Email: vmOwnerEmail, + }, + ) + if err != nil { + return fmt.Errorf("creating vm contact: %s", err) } - - // Update the vms with primary addresses - if nbPrimaryIPv4 != nil && (netboxVM.PrimaryIPv4 == nil || nbPrimaryIPv4.Address != netboxVM.PrimaryIPv4.Address) || nbPrimaryIPv6 != nil && (netboxVM.PrimaryIPv6 == nil || nbPrimaryIPv6.Address != netboxVM.PrimaryIPv6.Address) { - // Shallow copy netboxVm to newNetboxVM - newNetboxVM := *netboxVM - newNetboxVM.PrimaryIPv4 = nbPrimaryIPv4 - newNetboxVM.PrimaryIPv6 = nbPrimaryIPv6 - _, err = nbi.AddVM(&newNetboxVM) - if err != nil { - vc.Logger.Warningf("adding vm: %s", err) - } + _, err = nbi.AddContactAssignment(&objects.ContactAssignment{ + ContentType: "virtualization.virtualmachine", + ObjectID: nbVM.ID, + Contact: contact, + Role: nbi.ContactRolesIndexByName[objects.AdminContactRoleName], + }) + if err != nil { + return fmt.Errorf("add contact assignment for vm: %s", err) } } } - return nil }