From 10c85b983d68876c4f68614bf7182b5ab51556cf Mon Sep 17 00:00:00 2001 From: bl4ko Date: Tue, 12 Mar 2024 01:06:05 +0100 Subject: [PATCH] test: adding extra tests --- go.mod | 4 +- go.sum | 8 +- internal/constants/constants.go | 32 +++++ internal/netbox/inventory/add_items.go | 35 ++--- internal/netbox/inventory/add_items_test.go | 32 +++-- internal/netbox/inventory/init_items.go | 68 +++++----- internal/netbox/inventory/inventory.go | 34 ++--- internal/netbox/service/api_test.go | 101 -------------- internal/netbox/service/constants.go | 33 ----- internal/netbox/service/rest.go | 47 +++---- internal/netbox/service/rest_test.go | 140 +++++++++++++++++++- internal/netbox/service/test_objects.go | 135 +++++++++++++++++++ 12 files changed, 427 insertions(+), 242 deletions(-) delete mode 100644 internal/netbox/service/constants.go create mode 100644 internal/netbox/service/test_objects.go diff --git a/go.mod b/go.mod index 10574871..34304c40 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/jinzhu/copier v0.4.0 // indirect github.com/magefile/mage v1.15.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect gopkg.in/djherbis/times.v1 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 5a31c28a..bbb59922 100644 --- a/go.sum +++ b/go.sum @@ -55,8 +55,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -69,8 +69,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 92c13ef5..cd4d78b1 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -99,3 +99,35 @@ const ( MaxVID = 4094 TaggedVID = 4095 ) + +// Here all mappings are defined so we don't hardcode api paths of objects +// in our code. +const ( + ContactGroupsAPIPath = "/api/tenancy/contact-groups/" + ContactRolesAPIPath = "/api/tenancy/contact-roles/" + ContactsAPIPath = "/api/tenancy/contacts/" + TenantsAPIPath = "/api/tenancy/tenants/" + ContactAssignmentsAPIPath = "/api/tenancy/contact-assignments/" + + PrefixesAPIPath = "/api/ipam/prefixes/" + VlanGroupsAPIPath = "/api/ipam/vlan-groups/" + VlansAPIPath = "/api/ipam/vlans/" + IPAddressesAPIPath = "/api/ipam/ip-addresses/" + + ClusterTypesAPIPath = "/api/virtualization/cluster-types/" + ClusterGroupsAPIPath = "/api/virtualization/cluster-groups/" + ClustersAPIPath = "/api/virtualization/clusters/" + VirtualMachinesAPIPath = "/api/virtualization/virtual-machines/" + VMInterfacesAPIPath = "/api/virtualization/interfaces/" + + DevicesAPIPath = "/api/dcim/devices/" + DeviceRolesAPIPath = "/api/dcim/device-roles/" + DeviceTypesAPIPath = "/api/dcim/device-types/" + InterfacesAPIPath = "/api/dcim/interfaces/" + SitesAPIPath = "/api/dcim/sites/" + ManufacturersAPIPath = "/api/dcim/manufacturers/" + PlatformsAPIPath = "/api/dcim/platforms/" + + CustomFieldsAPIPath = "/api/extras/custom-fields/" + TagsAPIPath = "/api/extras/tags/" +) diff --git a/internal/netbox/inventory/add_items.go b/internal/netbox/inventory/add_items.go index 96544fde..9717588c 100644 --- a/internal/netbox/inventory/add_items.go +++ b/internal/netbox/inventory/add_items.go @@ -3,6 +3,7 @@ package inventory import ( "context" + "github.com/bl4ko/netbox-ssot/internal/constants" "github.com/bl4ko/netbox-ssot/internal/netbox/objects" "github.com/bl4ko/netbox-ssot/internal/netbox/service" "github.com/bl4ko/netbox-ssot/internal/utils" @@ -176,7 +177,7 @@ func (nbi *NetboxInventory) AddContact(ctx context.Context, newContact *objects. defer nbi.ContactsLock.Unlock() if _, ok := nbi.ContactsIndexByName[newContact.Name]; ok { oldContact := nbi.ContactsIndexByName[newContact.Name] - delete(nbi.OrphanManager[service.ContactsAPIPath], oldContact.ID) + delete(nbi.OrphanManager[constants.ContactsAPIPath], oldContact.ID) diffMap, err := utils.JSONDiffMapExceptID(newContact, oldContact, false, nbi.SourcePriority) if err != nil { return nil, err @@ -219,7 +220,7 @@ func (nbi *NetboxInventory) AddContactAssignment(ctx context.Context, newCA *obj newCA.Tags = append(newCA.Tags, nbi.SsotTag) if _, ok := nbi.ContactAssignmentsIndexByContentTypeAndObjectIDAndContactIDAndRoleID[newCA.ContentType][newCA.ObjectID][newCA.Contact.ID][newCA.Role.ID]; ok { oldCA := nbi.ContactAssignmentsIndexByContentTypeAndObjectIDAndContactIDAndRoleID[newCA.ContentType][newCA.ObjectID][newCA.Contact.ID][newCA.Role.ID] - delete(nbi.OrphanManager[service.ContactAssignmentsAPIPath], oldCA.ID) + delete(nbi.OrphanManager[constants.ContactAssignmentsAPIPath], oldCA.ID) diffMap, err := utils.JSONDiffMapExceptID(newCA, oldCA, false, nbi.SourcePriority) if err != nil { return nil, err @@ -282,7 +283,7 @@ func (nbi *NetboxInventory) AddClusterGroup(ctx context.Context, newCg *objects. if _, ok := nbi.ClusterGroupsIndexByName[newCg.Name]; ok { // Remove id from orphan manager, because it still exists in the sources oldCg := nbi.ClusterGroupsIndexByName[newCg.Name] - delete(nbi.OrphanManager[service.ClusterGroupsAPIPath], oldCg.ID) + delete(nbi.OrphanManager[constants.ClusterGroupsAPIPath], oldCg.ID) diffMap, err := utils.JSONDiffMapExceptID(newCg, oldCg, false, nbi.SourcePriority) if err != nil { return nil, err @@ -316,7 +317,7 @@ func (nbi *NetboxInventory) AddClusterType(ctx context.Context, newClusterType * if _, ok := nbi.ClusterTypesIndexByName[newClusterType.Name]; ok { // Remove id from orphan manager, because it still exists in the sources oldClusterType := nbi.ClusterTypesIndexByName[newClusterType.Name] - delete(nbi.OrphanManager[service.ClusterTypesAPIPath], oldClusterType.ID) + delete(nbi.OrphanManager[constants.ClusterTypesAPIPath], oldClusterType.ID) diffMap, err := utils.JSONDiffMapExceptID(newClusterType, oldClusterType, false, nbi.SourcePriority) if err != nil { return nil, err @@ -351,7 +352,7 @@ func (nbi *NetboxInventory) AddCluster(ctx context.Context, newCluster *objects. if _, ok := nbi.ClustersIndexByName[newCluster.Name]; ok { // Remove id from orphan manager, because it still exists in the sources oldCluster := nbi.ClustersIndexByName[newCluster.Name] - delete(nbi.OrphanManager[service.ClustersAPIPath], oldCluster.ID) + delete(nbi.OrphanManager[constants.ClustersAPIPath], oldCluster.ID) diffMap, err := utils.JSONDiffMapExceptID(newCluster, oldCluster, false, nbi.SourcePriority) if err != nil { return nil, err @@ -384,7 +385,7 @@ func (nbi *NetboxInventory) AddDeviceRole(ctx context.Context, newDeviceRole *ob if _, ok := nbi.DeviceRolesIndexByName[newDeviceRole.Name]; ok { // Remove id from orphan manager, because it still exists in the sources oldDeviceRole := nbi.DeviceRolesIndexByName[newDeviceRole.Name] - delete(nbi.OrphanManager[service.DeviceRolesAPIPath], nbi.DeviceRolesIndexByName[newDeviceRole.Name].ID) + delete(nbi.OrphanManager[constants.DeviceRolesAPIPath], nbi.DeviceRolesIndexByName[newDeviceRole.Name].ID) diffMap, err := utils.JSONDiffMapExceptID(newDeviceRole, oldDeviceRole, false, nbi.SourcePriority) if err != nil { return nil, err @@ -417,7 +418,7 @@ func (nbi *NetboxInventory) AddManufacturer(ctx context.Context, newManufacturer if _, ok := nbi.ManufacturersIndexByName[newManufacturer.Name]; ok { // Remove id from orphan manager, because it still exists in the sources oldManufacturer := nbi.ManufacturersIndexByName[newManufacturer.Name] - delete(nbi.OrphanManager[service.ManufacturersAPIPath], oldManufacturer.ID) + delete(nbi.OrphanManager[constants.ManufacturersAPIPath], oldManufacturer.ID) diffMap, err := utils.JSONDiffMapExceptID(newManufacturer, oldManufacturer, false, nbi.SourcePriority) if err != nil { return nil, err @@ -450,7 +451,7 @@ func (nbi *NetboxInventory) AddDeviceType(ctx context.Context, newDeviceType *ob if _, ok := nbi.DeviceTypesIndexByModel[newDeviceType.Model]; ok { // Remove id from orphan manager, because it still exists in the sources oldDeviceType := nbi.DeviceTypesIndexByModel[newDeviceType.Model] - delete(nbi.OrphanManager[service.DeviceTypesAPIPath], oldDeviceType.ID) + delete(nbi.OrphanManager[constants.DeviceTypesAPIPath], oldDeviceType.ID) diffMap, err := utils.JSONDiffMapExceptID(newDeviceType, oldDeviceType, false, nbi.SourcePriority) if err != nil { return nil, err @@ -483,7 +484,7 @@ func (nbi *NetboxInventory) AddPlatform(ctx context.Context, newPlatform *object if _, ok := nbi.PlatformsIndexByName[newPlatform.Name]; ok { // Remove id from orphan manager, because it still exists in the sources oldPlatform := nbi.PlatformsIndexByName[newPlatform.Name] - delete(nbi.OrphanManager[service.PlatformsAPIPath], oldPlatform.ID) + delete(nbi.OrphanManager[constants.PlatformsAPIPath], oldPlatform.ID) diffMap, err := utils.JSONDiffMapExceptID(newPlatform, oldPlatform, false, nbi.SourcePriority) if err != nil { return nil, err @@ -515,7 +516,7 @@ func (nbi *NetboxInventory) AddDevice(ctx context.Context, newDevice *objects.De newDevice.Tags = append(newDevice.Tags, nbi.SsotTag) if _, ok := nbi.DevicesIndexByNameAndSiteID[newDevice.Name][newDevice.Site.ID]; ok { oldDevice := nbi.DevicesIndexByNameAndSiteID[newDevice.Name][newDevice.Site.ID] - delete(nbi.OrphanManager[service.DevicesAPIPath], oldDevice.ID) + delete(nbi.OrphanManager[constants.DevicesAPIPath], oldDevice.ID) diffMap, err := utils.JSONDiffMapExceptID(newDevice, oldDevice, false, nbi.SourcePriority) if err != nil { return nil, err @@ -551,7 +552,7 @@ func (nbi *NetboxInventory) AddVlanGroup(ctx context.Context, newVlanGroup *obje if _, ok := nbi.VlanGroupsIndexByName[newVlanGroup.Name]; ok { // Remove id from orphan manager, because it still exists in the sources oldVlanGroup := nbi.VlanGroupsIndexByName[newVlanGroup.Name] - delete(nbi.OrphanManager[service.VlanGroupsAPIPath], oldVlanGroup.ID) + delete(nbi.OrphanManager[constants.VlanGroupsAPIPath], oldVlanGroup.ID) diffMap, err := utils.JSONDiffMapExceptID(newVlanGroup, oldVlanGroup, false, nbi.SourcePriority) if err != nil { return nil, err @@ -584,7 +585,7 @@ func (nbi *NetboxInventory) AddVlan(ctx context.Context, newVlan *objects.Vlan) if _, ok := nbi.VlansIndexByVlanGroupIDAndVID[newVlan.Group.ID][newVlan.Vid]; ok { // Remove id from orphan manager, because it still exists in the sources oldVlan := nbi.VlansIndexByVlanGroupIDAndVID[newVlan.Group.ID][newVlan.Vid] - delete(nbi.OrphanManager[service.VlansAPIPath], oldVlan.ID) + delete(nbi.OrphanManager[constants.VlansAPIPath], oldVlan.ID) diffMap, err := utils.JSONDiffMapExceptID(newVlan, oldVlan, false, nbi.SourcePriority) if err != nil { return nil, err @@ -619,7 +620,7 @@ func (nbi *NetboxInventory) AddInterface(ctx context.Context, newInterface *obje newInterface.Tags = append(newInterface.Tags, nbi.SsotTag) if _, ok := nbi.InterfacesIndexByDeviceIDAndName[newInterface.Device.ID][newInterface.Name]; ok { // Remove id from orphan manager, because it still exists in the sources - delete(nbi.OrphanManager[service.InterfacesAPIPath], nbi.InterfacesIndexByDeviceIDAndName[newInterface.Device.ID][newInterface.Name].ID) + delete(nbi.OrphanManager[constants.InterfacesAPIPath], nbi.InterfacesIndexByDeviceIDAndName[newInterface.Device.ID][newInterface.Name].ID) diffMap, err := utils.JSONDiffMapExceptID(newInterface, nbi.InterfacesIndexByDeviceIDAndName[newInterface.Device.ID][newInterface.Name], false, nbi.SourcePriority) oldIntf := nbi.InterfacesIndexByDeviceIDAndName[newInterface.Device.ID][newInterface.Name] if err != nil { @@ -655,7 +656,7 @@ func (nbi *NetboxInventory) AddVM(ctx context.Context, newVM *objects.VM) (*obje newVM.Tags = append(newVM.Tags, nbi.SsotTag) if _, ok := nbi.VMsIndexByName[newVM.Name]; ok { // Remove id from orphan manager, because it still exists in the sources - delete(nbi.OrphanManager[service.VirtualMachinesAPIPath], nbi.VMsIndexByName[newVM.Name].ID) + delete(nbi.OrphanManager[constants.VirtualMachinesAPIPath], nbi.VMsIndexByName[newVM.Name].ID) diffMap, err := utils.JSONDiffMapExceptID(newVM, nbi.VMsIndexByName[newVM.Name], false, nbi.SourcePriority) oldVM := nbi.VMsIndexByName[newVM.Name] if err != nil { @@ -689,7 +690,7 @@ func (nbi *NetboxInventory) AddVMInterface(ctx context.Context, newVMInterface * defer nbi.VMInterfacesLock.Unlock() if _, ok := nbi.VMInterfacesIndexByVMIdAndName[newVMInterface.VM.ID][newVMInterface.Name]; ok { // Remove id from orphan manager, because it still exists in the sources - delete(nbi.OrphanManager[service.VMInterfacesAPIPath], nbi.VMInterfacesIndexByVMIdAndName[newVMInterface.VM.ID][newVMInterface.Name].ID) + delete(nbi.OrphanManager[constants.VMInterfacesAPIPath], nbi.VMInterfacesIndexByVMIdAndName[newVMInterface.VM.ID][newVMInterface.Name].ID) diffMap, err := utils.JSONDiffMapExceptID(newVMInterface, nbi.VMInterfacesIndexByVMIdAndName[newVMInterface.VM.ID][newVMInterface.Name], false, nbi.SourcePriority) oldVMIface := nbi.VMInterfacesIndexByVMIdAndName[newVMInterface.VM.ID][newVMInterface.Name] if err != nil { @@ -725,7 +726,7 @@ func (nbi *NetboxInventory) AddIPAddress(ctx context.Context, newIPAddress *obje defer nbi.IPAddressesLock.Unlock() if _, ok := nbi.IPAdressesIndexByAddress[newIPAddress.Address]; ok { // Delete id from orphan manager, because it still exists in the sources - delete(nbi.OrphanManager[service.IPAddressesAPIPath], nbi.IPAdressesIndexByAddress[newIPAddress.Address].ID) + delete(nbi.OrphanManager[constants.IPAddressesAPIPath], nbi.IPAdressesIndexByAddress[newIPAddress.Address].ID) diffMap, err := utils.JSONDiffMapExceptID(newIPAddress, nbi.IPAdressesIndexByAddress[newIPAddress.Address], false, nbi.SourcePriority) oldIPAddress := nbi.IPAdressesIndexByAddress[newIPAddress.Address] if err != nil { @@ -759,7 +760,7 @@ func (nbi *NetboxInventory) AddPrefix(ctx context.Context, newPrefix *objects.Pr defer nbi.PrefixesLock.Unlock() if _, ok := nbi.PrefixesIndexByPrefix[newPrefix.Prefix]; ok { // Delete id from orphan manager, because it still exists in the sources - delete(nbi.OrphanManager[service.PrefixesAPIPath], nbi.PrefixesIndexByPrefix[newPrefix.Prefix].ID) + delete(nbi.OrphanManager[constants.PrefixesAPIPath], nbi.PrefixesIndexByPrefix[newPrefix.Prefix].ID) diffMap, err := utils.JSONDiffMapExceptID(newPrefix, nbi.PrefixesIndexByPrefix[newPrefix.Prefix], false, nbi.SourcePriority) oldPrefix := nbi.PrefixesIndexByPrefix[newPrefix.Prefix] if err != nil { diff --git a/internal/netbox/inventory/add_items_test.go b/internal/netbox/inventory/add_items_test.go index 9e1fd3dd..c11bed44 100644 --- a/internal/netbox/inventory/add_items_test.go +++ b/internal/netbox/inventory/add_items_test.go @@ -2,21 +2,24 @@ package inventory import ( "context" + "encoding/json" "log" "os" "reflect" "sync" "testing" + "github.com/bl4ko/netbox-ssot/internal/constants" "github.com/bl4ko/netbox-ssot/internal/logger" "github.com/bl4ko/netbox-ssot/internal/netbox/objects" + "github.com/bl4ko/netbox-ssot/internal/netbox/service" ) var MockInventory = &NetboxInventory{ Logger: &logger.Logger{Logger: log.New(os.Stdout, "", log.LstdFlags)}, TagsIndexByName: make(map[string]*objects.Tag), TagsLock: sync.Mutex{}, - NetboxAPI: nil, // TODO + NetboxAPI: service.MockNetboxClient, } func TestNetboxInventory_AddTag(t *testing.T) { @@ -28,26 +31,35 @@ func TestNetboxInventory_AddTag(t *testing.T) { name string nbi *NetboxInventory args args - want *objects.Tag + want string wantErr bool }{ - // { - // name: "Test new tag add", - // nbi: MockInventory, - // args: args{ctx: context.WithValue(context.Background(), constants.CtxSourceKey, "test"), newTag: &objects.Tag{Name: "new tag", Description: "New Tag", Color: objects.ColorBlack, Slug: "new_tag"}}, - // want: &objects.Tag{Name: "new tag", Description: "New Tag", Color: objects.ColorBlack, Slug: "new_tag"}, - // }, + { + name: "Test new tag add", + nbi: MockInventory, + args: args{ctx: context.WithValue(context.Background(), constants.CtxSourceKey, "test"), newTag: &objects.Tag{Name: "new tag", Description: "New Tag", Color: objects.ColorBlack, Slug: "new_tag"}}, + want: service.TagCreateResponse, + }, } + mockServer := service.CreateMockServer() + defer mockServer.Close() + for _, tt := range tests { + service.MockNetboxClient.BaseURL = mockServer.URL t.Run(tt.name, func(t *testing.T) { got, err := tt.nbi.AddTag(tt.args.ctx, tt.args.newTag) if (err != nil) != tt.wantErr { t.Errorf("NetboxInventory.AddTag() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NetboxInventory.AddTag() = %v, want %v", got, tt.want) + var wantTag objects.Tag + err = json.Unmarshal([]byte(tt.want), &wantTag) + if err != nil { + t.Errorf("unmarshal test data: %s", err) + } + if !reflect.DeepEqual(got, &wantTag) { + t.Errorf("NetboxInventory.AddTag() = %v, want %v", got, wantTag) } }) } diff --git a/internal/netbox/inventory/init_items.go b/internal/netbox/inventory/init_items.go index 1e344349..0fb07288 100644 --- a/internal/netbox/inventory/init_items.go +++ b/internal/netbox/inventory/init_items.go @@ -67,12 +67,12 @@ func (nbi *NetboxInventory) InitContacts(ctx context.Context) error { } // We also create an index of contacts by name for easier access nbi.ContactsIndexByName = make(map[string]*objects.Contact) - nbi.OrphanManager[service.ContactsAPIPath] = make(map[int]bool, len(nbContacts)) + nbi.OrphanManager[constants.ContactsAPIPath] = make(map[int]bool, len(nbContacts)) for i := range nbContacts { contact := &nbContacts[i] nbi.ContactsIndexByName[contact.Name] = contact if slices.IndexFunc(contact.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.ContactsAPIPath][contact.ID] = true + nbi.OrphanManager[constants.ContactsAPIPath][contact.ID] = true } } nbi.Logger.Debug(ctx, "Successfully collected contacts from Netbox: ", nbi.ContactsIndexByName) @@ -102,7 +102,7 @@ func (nbi *NetboxInventory) InitContactAssignments(ctx context.Context) error { } // We also create an index of contacts by name for easier access nbi.ContactAssignmentsIndexByContentTypeAndObjectIDAndContactIDAndRoleID = make(map[string]map[int]map[int]map[int]*objects.ContactAssignment) - nbi.OrphanManager[service.ContactAssignmentsAPIPath] = make(map[int]bool, len(nbCAs)) + nbi.OrphanManager[constants.ContactAssignmentsAPIPath] = make(map[int]bool, len(nbCAs)) debugIDs := map[int]bool{} // Netbox pagination bug duplicates for i := range nbCAs { cA := &nbCAs[i] @@ -121,7 +121,7 @@ func (nbi *NetboxInventory) InitContactAssignments(ctx context.Context) error { } nbi.ContactAssignmentsIndexByContentTypeAndObjectIDAndContactIDAndRoleID[cA.ContentType][cA.ObjectID][cA.Contact.ID][cA.Role.ID] = cA if slices.IndexFunc(cA.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.ContactAssignmentsAPIPath][cA.ID] = true + nbi.OrphanManager[constants.ContactAssignmentsAPIPath][cA.ID] = true } } nbi.Logger.Debug(ctx, "Successfully collected contacts from Netbox: ", nbi.ContactsIndexByName) @@ -187,13 +187,13 @@ func (nbi *NetboxInventory) InitManufacturers(ctx context.Context) error { // Initialize internal index of manufacturers by name nbi.ManufacturersIndexByName = make(map[string]*objects.Manufacturer) // OrphanManager takes care of all manufacturers created by netbox-ssot - nbi.OrphanManager[service.ManufacturersAPIPath] = make(map[int]bool) + nbi.OrphanManager[constants.ManufacturersAPIPath] = make(map[int]bool) for i := range nbManufacturers { manufacturer := &nbManufacturers[i] nbi.ManufacturersIndexByName[manufacturer.Name] = manufacturer if slices.IndexFunc(manufacturer.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.ManufacturersAPIPath][manufacturer.ID] = true + nbi.OrphanManager[constants.ManufacturersAPIPath][manufacturer.ID] = true } } @@ -210,12 +210,12 @@ func (nbi *NetboxInventory) InitPlatforms(ctx context.Context) error { // Initialize internal index of platforms by name nbi.PlatformsIndexByName = make(map[string]*objects.Platform) // OrphanManager takes care of all platforms created by netbox-ssot - nbi.OrphanManager[service.PlatformsAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.PlatformsAPIPath] = make(map[int]bool, 0) for i, platform := range nbPlatforms { nbi.PlatformsIndexByName[platform.Name] = &nbPlatforms[i] if slices.IndexFunc(platform.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.PlatformsAPIPath][platform.ID] = true + nbi.OrphanManager[constants.PlatformsAPIPath][platform.ID] = true } } @@ -232,7 +232,7 @@ func (nbi *NetboxInventory) InitDevices(ctx context.Context) error { // Initialize internal index of devices by Name and SiteId nbi.DevicesIndexByNameAndSiteID = make(map[string]map[int]*objects.Device) // OrphanManager takes care of all devices created by netbox-ssot - nbi.OrphanManager[service.DevicesAPIPath] = make(map[int]bool) + nbi.OrphanManager[constants.DevicesAPIPath] = make(map[int]bool) for i, device := range nbDevices { if nbi.DevicesIndexByNameAndSiteID[device.Name] == nil { @@ -240,7 +240,7 @@ func (nbi *NetboxInventory) InitDevices(ctx context.Context) error { } nbi.DevicesIndexByNameAndSiteID[device.Name][device.Site.ID] = &nbDevices[i] if slices.IndexFunc(device.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.DevicesAPIPath][device.ID] = true + nbi.OrphanManager[constants.DevicesAPIPath][device.ID] = true } } @@ -258,13 +258,13 @@ func (nbi *NetboxInventory) InitDeviceRoles(ctx context.Context) error { // We also create an index of device roles by name for easier access nbi.DeviceRolesIndexByName = make(map[string]*objects.DeviceRole) // OrphanManager takes care of all device roles created by netbox-ssot - nbi.OrphanManager[service.DeviceRolesAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.DeviceRolesAPIPath] = make(map[int]bool, 0) for i := range nbDeviceRoles { deviceRole := &nbDeviceRoles[i] nbi.DeviceRolesIndexByName[deviceRole.Name] = deviceRole if slices.IndexFunc(deviceRole.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.DeviceRolesAPIPath][deviceRole.ID] = true + nbi.OrphanManager[constants.DeviceRolesAPIPath][deviceRole.ID] = true } } @@ -374,13 +374,13 @@ func (nbi *NetboxInventory) InitClusterGroups(ctx context.Context) error { // Initialize internal index of cluster groups by name nbi.ClusterGroupsIndexByName = make(map[string]*objects.ClusterGroup) // OrphanManager takes care of all cluster groups created by netbox-ssot - nbi.OrphanManager[service.ClusterGroupsAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.DeviceRolesAPIPath] = make(map[int]bool, 0) for i := range nbClusterGroups { clusterGroup := &nbClusterGroups[i] nbi.ClusterGroupsIndexByName[clusterGroup.Name] = clusterGroup if slices.IndexFunc(clusterGroup.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.ClusterGroupsAPIPath][clusterGroup.ID] = true + nbi.OrphanManager[constants.DeviceRolesAPIPath][clusterGroup.ID] = true } } nbi.Logger.Debug(ctx, "Successfully collected cluster groups from Netbox: ", nbi.ClusterGroupsIndexByName) @@ -397,13 +397,13 @@ func (nbi *NetboxInventory) InitClusterTypes(ctx context.Context) error { // Initialize internal index of cluster types by name nbi.ClusterTypesIndexByName = make(map[string]*objects.ClusterType) // OrphanManager takes care of all cluster types created by netbox-ssot - nbi.OrphanManager[service.ClusterTypesAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.ClusterTypesAPIPath] = make(map[int]bool, 0) for i := range nbClusterTypes { clusterType := &nbClusterTypes[i] nbi.ClusterTypesIndexByName[clusterType.Name] = clusterType if slices.IndexFunc(clusterType.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.ClusterTypesAPIPath][clusterType.ID] = true + nbi.OrphanManager[constants.ClusterTypesAPIPath][clusterType.ID] = true } } @@ -421,13 +421,13 @@ func (nbi *NetboxInventory) InitClusters(ctx context.Context) error { // Initialize internal index of clusters by name nbi.ClustersIndexByName = make(map[string]*objects.Cluster) // OrphanManager takes care of all clusters created by netbox-ssot - nbi.OrphanManager[service.ClustersAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.ClustersAPIPath] = make(map[int]bool, 0) for i := range nbClusters { cluster := &nbClusters[i] nbi.ClustersIndexByName[cluster.Name] = cluster if slices.IndexFunc(cluster.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.ClustersAPIPath][cluster.ID] = true + nbi.OrphanManager[constants.ClustersAPIPath][cluster.ID] = true } } @@ -444,13 +444,13 @@ func (nbi *NetboxInventory) InitDeviceTypes(ctx context.Context) error { // Initialize internal index of device types by model nbi.DeviceTypesIndexByModel = make(map[string]*objects.DeviceType) // OrphanManager takes care of all device types created by netbox-ssot - nbi.OrphanManager[service.DeviceTypesAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.DeviceTypesAPIPath] = make(map[int]bool, 0) for i := range nbDeviceTypes { deviceType := &nbDeviceTypes[i] nbi.DeviceTypesIndexByModel[deviceType.Model] = deviceType if slices.IndexFunc(deviceType.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.DeviceTypesAPIPath][deviceType.ID] = true + nbi.OrphanManager[constants.DeviceTypesAPIPath][deviceType.ID] = true } } @@ -468,7 +468,7 @@ func (nbi *NetboxInventory) InitInterfaces(ctx context.Context) error { // Initialize internal index of interfaces by device id and name nbi.InterfacesIndexByDeviceIDAndName = make(map[int]map[string]*objects.Interface) // OrphanManager takes care of all interfaces created by netbox-ssot - nbi.OrphanManager[service.InterfacesAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.InterfacesAPIPath] = make(map[int]bool, 0) for i := range nbInterfaces { intf := &nbInterfaces[i] @@ -477,7 +477,7 @@ func (nbi *NetboxInventory) InitInterfaces(ctx context.Context) error { } nbi.InterfacesIndexByDeviceIDAndName[intf.Device.ID][intf.Name] = intf if slices.IndexFunc(intf.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.InterfacesAPIPath][intf.ID] = true + nbi.OrphanManager[constants.InterfacesAPIPath][intf.ID] = true } } @@ -517,13 +517,13 @@ func (nbi *NetboxInventory) InitVlanGroups(ctx context.Context) error { // Initialize internal index of vlans by name nbi.VlanGroupsIndexByName = make(map[string]*objects.VlanGroup) // Add VlanGroups to orphan manager - nbi.OrphanManager[service.VlanGroupsAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.VlanGroupsAPIPath] = make(map[int]bool, 0) for i := range nbVlanGroups { vlanGroup := &nbVlanGroups[i] nbi.VlanGroupsIndexByName[vlanGroup.Name] = vlanGroup if slices.IndexFunc(vlanGroup.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.VlanGroupsAPIPath][vlanGroup.ID] = true + nbi.OrphanManager[constants.VlanGroupsAPIPath][vlanGroup.ID] = true } } @@ -541,7 +541,7 @@ func (nbi *NetboxInventory) InitVlans(ctx context.Context) error { // Initialize internal index of vlans by VlanGroupId and Vid nbi.VlansIndexByVlanGroupIDAndVID = make(map[int]map[int]*objects.Vlan) // Add vlans to orphan manager - nbi.OrphanManager[service.VlansAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.VlansAPIPath] = make(map[int]bool, 0) for i := range nbVlans { vlan := &nbVlans[i] @@ -559,7 +559,7 @@ func (nbi *NetboxInventory) InitVlans(ctx context.Context) error { } nbi.VlansIndexByVlanGroupIDAndVID[vlan.Group.ID][vlan.Vid] = vlan if slices.IndexFunc(vlan.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.VlansAPIPath][vlan.ID] = true + nbi.OrphanManager[constants.VlansAPIPath][vlan.ID] = true } } @@ -577,13 +577,13 @@ func (nbi *NetboxInventory) InitVMs(ctx context.Context) error { // Initialize internal index of VMs by name nbi.VMsIndexByName = make(map[string]*objects.VM) // Add VMs to orphan manager - nbi.OrphanManager[service.VirtualMachinesAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.VirtualMachinesAPIPath] = make(map[int]bool, 0) for i := range nbVMs { vm := &nbVMs[i] nbi.VMsIndexByName[vm.Name] = vm if slices.IndexFunc(vm.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.VirtualMachinesAPIPath][vm.ID] = true + nbi.OrphanManager[constants.VirtualMachinesAPIPath][vm.ID] = true } } @@ -601,7 +601,7 @@ func (nbi *NetboxInventory) InitVMInterfaces(ctx context.Context) error { // Initialize internal index of VM interfaces by VM id and name nbi.VMInterfacesIndexByVMIdAndName = make(map[int]map[string]*objects.VMInterface) // Add VMInterfaces to orphan manager - nbi.OrphanManager[service.VMInterfacesAPIPath] = make(map[int]bool) + nbi.OrphanManager[constants.VMInterfacesAPIPath] = make(map[int]bool) for i := range nbVMInterfaces { vmIntf := &nbVMInterfaces[i] @@ -610,7 +610,7 @@ func (nbi *NetboxInventory) InitVMInterfaces(ctx context.Context) error { } nbi.VMInterfacesIndexByVMIdAndName[vmIntf.VM.ID][vmIntf.Name] = vmIntf if slices.IndexFunc(vmIntf.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.VMInterfacesAPIPath][vmIntf.ID] = true + nbi.OrphanManager[constants.VMInterfacesAPIPath][vmIntf.ID] = true } } @@ -628,13 +628,13 @@ func (nbi *NetboxInventory) InitIPAddresses(ctx context.Context) error { // Initializes internal index of IP addresses by address nbi.IPAdressesIndexByAddress = make(map[string]*objects.IPAddress) // Add IP addresses to orphan manager - nbi.OrphanManager[service.IPAddressesAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.IPAddressesAPIPath] = make(map[int]bool, 0) for i := range ipAddresses { ipAddr := &ipAddresses[i] nbi.IPAdressesIndexByAddress[ipAddr.Address] = ipAddr if slices.IndexFunc(ipAddr.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.IPAddressesAPIPath][ipAddr.ID] = true + nbi.OrphanManager[constants.IPAddressesAPIPath][ipAddr.ID] = true } } @@ -652,13 +652,13 @@ func (nbi *NetboxInventory) InitPrefixes(ctx context.Context) error { // Initializes internal index of prefixes by prefix nbi.PrefixesIndexByPrefix = make(map[string]*objects.Prefix) // Add prefixes to orphan manager - nbi.OrphanManager[service.PrefixesAPIPath] = make(map[int]bool, 0) + nbi.OrphanManager[constants.PrefixesAPIPath] = make(map[int]bool, 0) for i := range prefixes { prefix := &prefixes[i] nbi.PrefixesIndexByPrefix[prefix.Prefix] = prefix if slices.IndexFunc(prefix.Tags, func(t *objects.Tag) bool { return t.Slug == nbi.SsotTag.Slug }) >= 0 { - nbi.OrphanManager[service.PrefixesAPIPath][prefix.ID] = true + nbi.OrphanManager[constants.PrefixesAPIPath][prefix.ID] = true } } diff --git a/internal/netbox/inventory/inventory.go b/internal/netbox/inventory/inventory.go index b219ffc0..b5a6cb3c 100644 --- a/internal/netbox/inventory/inventory.go +++ b/internal/netbox/inventory/inventory.go @@ -6,6 +6,7 @@ import ( "sync" "time" + "github.com/bl4ko/netbox-ssot/internal/constants" "github.com/bl4ko/netbox-ssot/internal/logger" "github.com/bl4ko/netbox-ssot/internal/netbox/objects" "github.com/bl4ko/netbox-ssot/internal/netbox/service" @@ -142,23 +143,22 @@ func NewNetboxInventory(ctx context.Context, logger *logger.Logger, nbConfig *pa } // Starts with 0 for easier integration with for loops orphanObjectPriority := map[int]string{ - 0: service.VlanGroupsAPIPath, - 1: service.PrefixesAPIPath, - 2: service.VlansAPIPath, - 3: service.IPAddressesAPIPath, - 4: service.InterfacesAPIPath, - 5: service.VMInterfacesAPIPath, - 6: service.VirtualMachinesAPIPath, - 7: service.DevicesAPIPath, - 8: service.PlatformsAPIPath, - 9: service.DeviceTypesAPIPath, - 10: service.ManufacturersAPIPath, - 11: service.DeviceRolesAPIPath, - 12: service.ClustersAPIPath, - 13: service.ClusterTypesAPIPath, - 14: service.ClusterGroupsAPIPath, - 15: service.ContactAssignmentsAPIPath, - 16: service.ContactsAPIPath, + 0: constants.VlanGroupsAPIPath, + 1: constants.PrefixesAPIPath, + 2: constants.VlansAPIPath, + 3: constants.IPAddressesAPIPath, + 4: constants.InterfacesAPIPath, + 5: constants.VMInterfacesAPIPath, + 6: constants.VirtualMachinesAPIPath, + 7: constants.DevicesAPIPath, + 8: constants.PlatformsAPIPath, + 9: constants.DeviceTypesAPIPath, + 10: constants.ManufacturersAPIPath, + 11: constants.DeviceRolesAPIPath, + 12: constants.ClustersAPIPath, + 13: constants.ClusterTypesAPIPath, + 14: constants.ContactAssignmentsAPIPath, + 15: constants.ContactsAPIPath, } nbi := &NetboxInventory{Ctx: ctx, Logger: logger, NetboxConfig: nbConfig, SourcePriority: sourcePriority, OrphanManager: make(map[string]map[int]bool), OrphanObjectPriority: orphanObjectPriority} return nbi diff --git a/internal/netbox/service/api_test.go b/internal/netbox/service/api_test.go index 66ef67ed..06e15e2a 100644 --- a/internal/netbox/service/api_test.go +++ b/internal/netbox/service/api_test.go @@ -3,11 +3,9 @@ package service import ( "context" "crypto/tls" - "fmt" "io" "log" "net/http" - "net/http/httptest" "reflect" "testing" @@ -15,105 +13,6 @@ import ( "github.com/bl4ko/netbox-ssot/internal/logger" ) -// Hardcoded api return values. -const ( - VersionResponse = "{\"django-version\": \"4.2.10\"}" - TagsResponse = "{\"count\":2,\"next\":null,\"previous\":null,\"results\":[{\"id\":34,\"url\":\"http://netbox.example.com/api/extras/tags/34/\",\"display\":\"Source: proxmox\",\"name\":\"Source: proxmox\",\"slug\":\"source-proxmox\",\"color\":\"9e9e9e\",\"description\":\"Automatically created tag by netbox-ssot for source proxmox\",\"object_types\":[],\"tagged_items\":115,\"created\":\"2024-02-25T16:52:51.691729Z\",\"last_updated\":\"2024-02-25T16:52:51.691743Z\"},{\"id\":1,\"url\":\"http://netbox.example.com/api/extras/tags/1/\",\"display\":\"netbox-ssot\",\"name\":\"netbox-ssot\",\"slug\":\"netbox-ssot\",\"color\":\"00add8\",\"description\":\"Tag used by netbox-ssot to mark devices that are managed by it\",\"object_types\":[],\"tagged_items\":134,\"created\":\"2024-02-11T16:23:17.082244Z\",\"last_updated\":\"2024-02-11T16:23:17.082257Z\"}]}" -) - -func CreateMockServer() *httptest.Server { - handler := http.NewServeMux() - // Define handler for a specific path e.g., "/api/path" - handler.HandleFunc("/api/status/", func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, err := io.WriteString(w, VersionResponse) // Mock JSON Response - if err != nil { - log.Printf("Error writing response: %v", err) - } - }) - - handler.HandleFunc(TagsAPIPath, func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, err := io.WriteString(w, TagsResponse) - if err != nil { - log.Printf("Error writing response") - } - }) - - handler.HandleFunc(fmt.Sprintf("%s?limit=100&offset=0", TagsAPIPath), func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, err := io.WriteString(w, TagsResponse) - if err != nil { - log.Printf("Error writing response") - } - }) - - handler.HandleFunc("/api/read-error", func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusInternalServerError) // or any relevant status - //nolint:all - w.(http.Flusher).Flush() // Flush the headers to client - // Do not write any body, let the client read from the FaultyReader - }) - // Wildcard handler for all other paths - handler.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, err := io.WriteString(w, `{"error": "page not found"}`) - if err != nil { - log.Printf("Error writing response: %v", err) - } - }) - // More handlers can be added here for different paths or methods - return httptest.NewServer(handler) -} - -var MockNetboxClient = &NetboxClient{ - HTTPClient: &http.Client{}, - Logger: &logger.Logger{Logger: log.Default()}, - BaseURL: "", - APIToken: "testtoken", - Timeout: constants.DefaultAPITimeout, -} - -var FailingMockNetboxClient = &NetboxClient{ - HTTPClient: &http.Client{Transport: &FailingHTTPClient{}}, - Logger: &logger.Logger{Logger: log.Default()}, - BaseURL: "", - APIToken: "testtoken", - Timeout: constants.DefaultAPITimeout, -} - -type FailingHTTPClient struct{} - -func (m *FailingHTTPClient) RoundTrip(_ *http.Request) (*http.Response, error) { - // Return an error to simulate a failure in the HTTP request - return nil, fmt.Errorf("mock error") -} - -var MockNetboxClientWithReadError = &NetboxClient{ - HTTPClient: &http.Client{Transport: &FailingHTTPClientRead{}}, - Logger: &logger.Logger{Logger: log.Default()}, - BaseURL: "", - APIToken: "testtoken", - Timeout: constants.DefaultAPITimeout, -} - -type FailingHTTPClientRead struct{} - -func (m *FailingHTTPClientRead) RoundTrip(_ *http.Request) (*http.Response, error) { - // Simulate a response with a FaultyReader as its Body - return &http.Response{ - StatusCode: http.StatusInternalServerError, - Body: io.NopCloser(&FaultyReader{}), - Header: make(http.Header), - }, nil -} - -type FaultyReader struct{} - -func (m *FaultyReader) Read(_ []byte) (n int, err error) { - return 0, fmt.Errorf("mock read error") -} - func TestNewNetBoxAPI(t *testing.T) { type args struct { ctx context.Context diff --git a/internal/netbox/service/constants.go b/internal/netbox/service/constants.go deleted file mode 100644 index 02c9dab8..00000000 --- a/internal/netbox/service/constants.go +++ /dev/null @@ -1,33 +0,0 @@ -package service - -// Here all mappings are defined so we don't hardcode api paths of objects -// in our code. -const ( - ContactGroupsAPIPath = "/api/tenancy/contact-groups/" - ContactRolesAPIPath = "/api/tenancy/contact-roles/" - ContactsAPIPath = "/api/tenancy/contacts/" - TenantsAPIPath = "/api/tenancy/tenants/" - ContactAssignmentsAPIPath = "/api/tenancy/contact-assignments/" - - PrefixesAPIPath = "/api/ipam/prefixes/" - VlanGroupsAPIPath = "/api/ipam/vlan-groups/" - VlansAPIPath = "/api/ipam/vlans/" - IPAddressesAPIPath = "/api/ipam/ip-addresses/" - - ClusterTypesAPIPath = "/api/virtualization/cluster-types/" - ClusterGroupsAPIPath = "/api/virtualization/cluster-groups/" - ClustersAPIPath = "/api/virtualization/clusters/" - VirtualMachinesAPIPath = "/api/virtualization/virtual-machines/" - VMInterfacesAPIPath = "/api/virtualization/interfaces/" - - DevicesAPIPath = "/api/dcim/devices/" - DeviceRolesAPIPath = "/api/dcim/device-roles/" - DeviceTypesAPIPath = "/api/dcim/device-types/" - InterfacesAPIPath = "/api/dcim/interfaces/" - SitesAPIPath = "/api/dcim/sites/" - ManufacturersAPIPath = "/api/dcim/manufacturers/" - PlatformsAPIPath = "/api/dcim/platforms/" - - CustomFieldsAPIPath = "/api/extras/custom-fields/" - TagsAPIPath = "/api/extras/tags/" -) diff --git a/internal/netbox/service/rest.go b/internal/netbox/service/rest.go index 2b656881..6ea61a27 100644 --- a/internal/netbox/service/rest.go +++ b/internal/netbox/service/rest.go @@ -8,6 +8,7 @@ import ( "net/http" "reflect" + "github.com/bl4ko/netbox-ssot/internal/constants" "github.com/bl4ko/netbox-ssot/internal/netbox/objects" "github.com/bl4ko/netbox-ssot/internal/utils" ) @@ -21,29 +22,29 @@ type Response[T any] struct { } var type2path = map[reflect.Type]string{ - reflect.TypeOf((*objects.VlanGroup)(nil)).Elem(): VlanGroupsAPIPath, - reflect.TypeOf((*objects.Vlan)(nil)).Elem(): VlansAPIPath, - reflect.TypeOf((*objects.IPAddress)(nil)).Elem(): IPAddressesAPIPath, - reflect.TypeOf((*objects.ClusterType)(nil)).Elem(): ClusterTypesAPIPath, - reflect.TypeOf((*objects.ClusterGroup)(nil)).Elem(): ClusterGroupsAPIPath, - reflect.TypeOf((*objects.Cluster)(nil)).Elem(): ClustersAPIPath, - reflect.TypeOf((*objects.VM)(nil)).Elem(): VirtualMachinesAPIPath, - reflect.TypeOf((*objects.VMInterface)(nil)).Elem(): VMInterfacesAPIPath, - reflect.TypeOf((*objects.Device)(nil)).Elem(): DevicesAPIPath, - reflect.TypeOf((*objects.DeviceRole)(nil)).Elem(): DeviceRolesAPIPath, - reflect.TypeOf((*objects.DeviceType)(nil)).Elem(): DeviceTypesAPIPath, - reflect.TypeOf((*objects.Interface)(nil)).Elem(): InterfacesAPIPath, - reflect.TypeOf((*objects.Site)(nil)).Elem(): SitesAPIPath, - reflect.TypeOf((*objects.Manufacturer)(nil)).Elem(): ManufacturersAPIPath, - reflect.TypeOf((*objects.Platform)(nil)).Elem(): PlatformsAPIPath, - reflect.TypeOf((*objects.Tenant)(nil)).Elem(): TenantsAPIPath, - reflect.TypeOf((*objects.ContactGroup)(nil)).Elem(): ContactGroupsAPIPath, - reflect.TypeOf((*objects.ContactRole)(nil)).Elem(): ContactRolesAPIPath, - reflect.TypeOf((*objects.Contact)(nil)).Elem(): ContactsAPIPath, - reflect.TypeOf((*objects.CustomField)(nil)).Elem(): CustomFieldsAPIPath, - reflect.TypeOf((*objects.Tag)(nil)).Elem(): TagsAPIPath, - reflect.TypeOf((*objects.ContactAssignment)(nil)).Elem(): ContactAssignmentsAPIPath, - reflect.TypeOf((*objects.Prefix)(nil)).Elem(): PrefixesAPIPath, + reflect.TypeOf((*objects.VlanGroup)(nil)).Elem(): constants.VlanGroupsAPIPath, + reflect.TypeOf((*objects.Vlan)(nil)).Elem(): constants.VlansAPIPath, + reflect.TypeOf((*objects.IPAddress)(nil)).Elem(): constants.IPAddressesAPIPath, + reflect.TypeOf((*objects.ClusterType)(nil)).Elem(): constants.ClusterTypesAPIPath, + reflect.TypeOf((*objects.ClusterGroup)(nil)).Elem(): constants.ClusterGroupsAPIPath, + reflect.TypeOf((*objects.Cluster)(nil)).Elem(): constants.ClustersAPIPath, + reflect.TypeOf((*objects.VM)(nil)).Elem(): constants.VirtualMachinesAPIPath, + reflect.TypeOf((*objects.VMInterface)(nil)).Elem(): constants.VMInterfacesAPIPath, + reflect.TypeOf((*objects.Device)(nil)).Elem(): constants.DevicesAPIPath, + reflect.TypeOf((*objects.DeviceRole)(nil)).Elem(): constants.DeviceRolesAPIPath, + reflect.TypeOf((*objects.DeviceType)(nil)).Elem(): constants.DeviceTypesAPIPath, + reflect.TypeOf((*objects.Interface)(nil)).Elem(): constants.InterfacesAPIPath, + reflect.TypeOf((*objects.Site)(nil)).Elem(): constants.SitesAPIPath, + reflect.TypeOf((*objects.Manufacturer)(nil)).Elem(): constants.ManufacturersAPIPath, + reflect.TypeOf((*objects.Platform)(nil)).Elem(): constants.PlatformsAPIPath, + reflect.TypeOf((*objects.Tenant)(nil)).Elem(): constants.TenantsAPIPath, + reflect.TypeOf((*objects.ContactGroup)(nil)).Elem(): constants.ContactGroupsAPIPath, + reflect.TypeOf((*objects.ContactRole)(nil)).Elem(): constants.ContactRolesAPIPath, + reflect.TypeOf((*objects.Contact)(nil)).Elem(): constants.ContactsAPIPath, + reflect.TypeOf((*objects.CustomField)(nil)).Elem(): constants.CustomFieldsAPIPath, + reflect.TypeOf((*objects.Tag)(nil)).Elem(): constants.TagsAPIPath, + reflect.TypeOf((*objects.ContactAssignment)(nil)).Elem(): constants.ContactAssignmentsAPIPath, + reflect.TypeOf((*objects.Prefix)(nil)).Elem(): constants.PrefixesAPIPath, } // GetAll queries all objects of type T from Netbox's API. diff --git a/internal/netbox/service/rest_test.go b/internal/netbox/service/rest_test.go index b26b4990..662d25c1 100644 --- a/internal/netbox/service/rest_test.go +++ b/internal/netbox/service/rest_test.go @@ -2,6 +2,7 @@ package service import ( "context" + "encoding/json" "reflect" "testing" @@ -31,7 +32,7 @@ func TestGetAll(t *testing.T) { // See predefined values in api_test for mockserver want: []objects.Tag{ { - ID: 34, + ID: 0, Name: "Source: proxmox", Slug: "source-proxmox", Color: "9e9e9e", @@ -65,3 +66,140 @@ func TestGetAll(t *testing.T) { }) } } + +func TestPatch(t *testing.T) { + type args struct { + ctx context.Context + api *NetboxClient + objectID int + body map[string]interface{} + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Test patch tag", + args: args{ + ctx: context.WithValue(context.Background(), constants.CtxSourceKey, "test"), + api: MockNetboxClient, + objectID: 1, + body: map[string]interface{}{ + "description": "new description", + }, + }, + // See predefined values in api_test for mockserver + want: TagPatchResponse, + wantErr: false, + }, + } + for _, tt := range tests { + mockServer := CreateMockServer() + defer mockServer.Close() + MockNetboxClient.BaseURL = mockServer.URL + t.Run(tt.name, func(t *testing.T) { + response, err := Patch[objects.Tag](tt.args.ctx, tt.args.api, tt.args.objectID, tt.args.body) + if (err != nil) != tt.wantErr { + t.Errorf("GetAll() error = %v, wantErr %v", err, tt.wantErr) + return + } + // Parse the object + var wantTag objects.Tag + err = json.Unmarshal([]byte(tt.want), &wantTag) + if err != nil { + t.Errorf("marshal tag patch response: %s", err) + } + if !reflect.DeepEqual(response, &wantTag) { + t.Errorf("Patch() = %v, want %v", response, wantTag) + } + }) + } +} + +func TestCreate(t *testing.T) { + type args struct { + ctx context.Context + api *NetboxClient + object string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Test create tag", + args: args{ + ctx: context.WithValue(context.Background(), constants.CtxSourceKey, "test"), + api: MockNetboxClient, + object: TagCreateResponse, + }, + // See predefined values in api_test for mockserver + want: TagPatchResponse, + wantErr: false, + }, + } + for _, tt := range tests { + mockServer := CreateMockServer() + defer mockServer.Close() + MockNetboxClient.BaseURL = mockServer.URL + t.Run(tt.name, func(t *testing.T) { + var newTag objects.Tag + err := json.Unmarshal([]byte(tt.args.object), &newTag) + if err != nil { + t.Errorf("unmarshal tag: %s", err) + } + response, err := Create[objects.Tag](tt.args.ctx, tt.args.api, &newTag) + if (err != nil) != tt.wantErr { + t.Errorf("GetAll() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(response, &newTag) { + t.Errorf("Patch() = %v, want %v", response, newTag) + } + }) + } +} + +func TestBulkDeleteObjects(t *testing.T) { + type args struct { + ctx context.Context + objectPath string + idSet map[int]bool + api *NetboxClient + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "Test bulk delete tags", + args: args{ + ctx: context.WithValue(context.Background(), constants.CtxSourceKey, "test"), + objectPath: constants.TagsAPIPath, + idSet: map[int]bool{0: true, 1: true}, + api: MockNetboxClient, + }, + // See predefined values in api_test for mockserver + want: TagPatchResponse, + wantErr: false, + }, + } + for _, tt := range tests { + mockServer := CreateMockServer() + defer mockServer.Close() + MockNetboxClient.BaseURL = mockServer.URL + t.Run(tt.name, func(t *testing.T) { + err := tt.args.api.BulkDeleteObjects(tt.args.ctx, tt.args.objectPath, tt.args.idSet) + if (err != nil) != tt.wantErr { + t.Errorf("GetAll() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +} diff --git a/internal/netbox/service/test_objects.go b/internal/netbox/service/test_objects.go new file mode 100644 index 00000000..ce76e00a --- /dev/null +++ b/internal/netbox/service/test_objects.go @@ -0,0 +1,135 @@ +package service + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "net/http/httptest" + + "github.com/bl4ko/netbox-ssot/internal/constants" + "github.com/bl4ko/netbox-ssot/internal/logger" + "github.com/bl4ko/netbox-ssot/internal/netbox/objects" +) + +// Hardcoded api return values. +const ( + VersionResponse = "{\"django-version\": \"4.2.10\"}" + TagsResponse = "{\"count\":2,\"next\":null,\"previous\":null,\"results\":[{\"id\":0,\"url\":\"http://netbox.example.com/api/extras/tags/34/\",\"display\":\"Source: proxmox\",\"name\":\"Source: proxmox\",\"slug\":\"source-proxmox\",\"color\":\"9e9e9e\",\"description\":\"Automatically created tag by netbox-ssot for source proxmox\",\"object_types\":[],\"tagged_items\":115,\"created\":\"2024-02-25T16:52:51.691729Z\",\"last_updated\":\"2024-02-25T16:52:51.691743Z\"},{\"id\":1,\"url\":\"http://netbox.example.com/api/extras/tags/1/\",\"display\":\"netbox-ssot\",\"name\":\"netbox-ssot\",\"slug\":\"netbox-ssot\",\"color\":\"00add8\",\"description\":\"Tag used by netbox-ssot to mark devices that are managed by it\",\"object_types\":[],\"tagged_items\":134,\"created\":\"2024-02-11T16:23:17.082244Z\",\"last_updated\":\"2024-02-11T16:23:17.082257Z\"}]}" + TagPatchResponse = "{\"id\":1,\"name\":\"netbox-ssot\",\"slug\":\"netbox-ssot\",\"color\":\"00add8\",\"description\":\"patched description\"}" + TagCreateResponse = "{\"id\":1,\"name\":\"netbox-ssot\",\"slug\":\"netbox-ssot\",\"color\":\"00add8\",\"description\":\"patched description\"}" +) + +func CreateMockServer() *httptest.Server { + handler := http.NewServeMux() + // Define handler for a specific path e.g., "/api/path" + handler.HandleFunc("/api/status/", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, err := io.WriteString(w, VersionResponse) // Mock JSON Response + if err != nil { + log.Printf("Error writing response: %v", err) + } + }) + + handler.HandleFunc(constants.TagsAPIPath, func(w http.ResponseWriter, r *http.Request) { + tagsIndex := map[int]objects.Tag{} + var tags []objects.Tag + err := json.Unmarshal([]byte(TagsResponse), &tags) + if err != nil { + log.Printf("error unmarshalling tags response: %s", err) + } + for _, tag := range tags { + tagsIndex[tag.ID] = tag + } + switch r.Method { + case http.MethodPatch: + // TODO: add logic + _, err = io.WriteString(w, TagPatchResponse) + if err != nil { + log.Printf("Error writing response: %v", err) + } + case http.MethodGet: + w.WriteHeader(http.StatusOK) + _, err := io.WriteString(w, TagsResponse) + if err != nil { + log.Printf("Error writing response") + } + case http.MethodPost: + w.WriteHeader(http.StatusCreated) + _, err := io.WriteString(w, TagCreateResponse) + if err != nil { + log.Printf("Error writing response") + } + case http.MethodDelete: + w.WriteHeader(http.StatusNoContent) + default: + log.Printf("Wrong http method: %v", r.Method) + } + }) + + handler.HandleFunc("/api/read-error", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) // or any relevant status + //nolint:all + w.(http.Flusher).Flush() // Flush the headers to client + // Do not write any body, let the client read from the FaultyReader + }) + // Wildcard handler for all other paths + handler.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, err := io.WriteString(w, `{"error": "page not found"}`) + if err != nil { + log.Printf("Error writing response: %v", err) + } + }) + // More handlers can be added here for different paths or methods + return httptest.NewServer(handler) +} + +var MockNetboxClient = &NetboxClient{ + HTTPClient: &http.Client{}, + Logger: &logger.Logger{Logger: log.Default()}, + BaseURL: "", + APIToken: "testtoken", + Timeout: constants.DefaultAPITimeout, +} + +var FailingMockNetboxClient = &NetboxClient{ + HTTPClient: &http.Client{Transport: &FailingHTTPClient{}}, + Logger: &logger.Logger{Logger: log.Default()}, + BaseURL: "", + APIToken: "testtoken", + Timeout: constants.DefaultAPITimeout, +} + +type FailingHTTPClient struct{} + +func (m *FailingHTTPClient) RoundTrip(_ *http.Request) (*http.Response, error) { + // Return an error to simulate a failure in the HTTP request + return nil, fmt.Errorf("mock error") +} + +var MockNetboxClientWithReadError = &NetboxClient{ + HTTPClient: &http.Client{Transport: &FailingHTTPClientRead{}}, + Logger: &logger.Logger{Logger: log.Default()}, + BaseURL: "", + APIToken: "testtoken", + Timeout: constants.DefaultAPITimeout, +} + +type FailingHTTPClientRead struct{} + +func (m *FailingHTTPClientRead) RoundTrip(_ *http.Request) (*http.Response, error) { + // Simulate a response with a FaultyReader as its Body + return &http.Response{ + StatusCode: http.StatusInternalServerError, + Body: io.NopCloser(&FaultyReader{}), + Header: make(http.Header), + }, nil +} + +type FaultyReader struct{} + +func (m *FaultyReader) Read(_ []byte) (n int, err error) { + return 0, fmt.Errorf("mock read error") +}